Library tanpa test adalah black box yang tidak terpercaya. Pada bagian ini, kita tulis unit test yang memverifikasi setiap fungsi API bekerja sesuai kontrak. Kita pakai pola test runner yang sama dengan proyek 7 dan 8: macro ASSERT yang menghitung pass/fail, dengan edge case yang mencakup input valid, invalid, dan boundary.
Konsep #
Unit test untuk library berbeda dari test aplikasi: kita tidak punya main.c yang dipakai pengguna. Test harus memverifikasi API publik secara langsung. Strategi yang kita pakai:
- Satu test function per API function —
test_mean,test_stddev,test_reverse_bytes, dst. - Edge case coverage — NULL input, n=0, nilai maksimum, buffer kecil.
- Floating point comparison —
fabs(actual - expected) < 1e-9untuk presisi double. - Smoke test version — pastikan
exlib_versionmengembalikan string yang benar.
Mekanisme kerja: setiap test function inisialisasi state lokal (tidak ada shared state), jalankan beberapa ASSERT, dan return. main() memanggil semua test function secara berurutan, lalu cetak ringkasan. Exit code 0 bila semua lulus, 1 bila ada yang gagal.
Implementasi #
Test runner tests/test_examplelib.c (ringkas):
/* test_examplelib.c - Unit test untuk examplelib */
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "examplelib.h"
static int tests_run = 0;
static int tests_pass = 0;
#define ASSERT(cond, msg) \
do { \
tests_run++; \
if (cond) { \
tests_pass++; \
} else { \
printf("FAIL: %s (line %d)\n", msg, __LINE__); \
} \
} while (0)
static void test_version(void) {
ASSERT(strcmp(exlib_version(), "1.0.0") == 0,
"versi harus 1.0.0");
ASSERT(strcmp(exlib_strerror(EXLIB_OK), "sukses") == 0,
"strerror OK = 'sukses'");
ASSERT(strcmp(exlib_strerror(EXLIB_ERR_NOT_FOUND),
"tidak ditemukan") == 0,
"strerror NOT_FOUND sesuai");
}
static void test_mean(void) {
double data[] = {1.0, 2.0, 3.0, 4.0, 5.0};
double mean = 0.0;
exlib_status s = exlib_mean(data, 5, &mean);
ASSERT(s == EXLIB_OK, "mean harus sukses");
ASSERT(fabs(mean - 3.0) < 1e-9, "mean = 3.0");
s = exlib_mean(NULL, 5, &mean);
ASSERT(s == EXLIB_ERR_NULL, "mean(NULL) = ERR_NULL");
s = exlib_mean(data, 0, &mean);
ASSERT(s == EXLIB_ERR_NULL, "mean n=0 = ERR_NULL");
}
static void test_stddev(void) {
double data[] = {2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0};
double sd = 0.0;
exlib_status s = exlib_stddev(data, 8, &sd);
ASSERT(s == EXLIB_OK, "stddev harus sukses");
/* Standar deviasi populasi: sqrt(32/8) = 2.0 */
ASSERT(fabs(sd - 2.0) < 1e-9, "stddev = 2.0");
}
static void test_reverse_bytes(void) {
uint8_t data[] = {1, 2, 3, 4, 5};
exlib_status s = exlib_reverse_bytes(data, 5);
ASSERT(s == EXLIB_OK, "reverse sukses");
ASSERT(data[0] == 5 && data[1] == 4 && data[2] == 3,
"data setelah reverse = 5,4,3,2,1");
ASSERT(data[3] == 2 && data[4] == 1, "data[3..4] = 2,1");
s = exlib_reverse_bytes(NULL, 5);
ASSERT(s == EXLIB_ERR_NULL, "reverse NULL = ERR_NULL");
uint8_t single[] = {42};
s = exlib_reverse_bytes(single, 1);
ASSERT(s == EXLIB_OK, "reverse n=1 = OK");
ASSERT(single[0] == 42, "single byte tidak berubah");
}
static void test_format_size(void) {
char buf[32];
exlib_status s = exlib_format_size(0, buf, sizeof(buf));
ASSERT(s == EXLIB_OK, "format 0 = OK");
ASSERT(strcmp(buf, "0.00 B") == 0, "0 bytes = '0.00 B'");
s = exlib_format_size(1024ULL, buf, sizeof(buf));
ASSERT(s == EXLIB_OK, "format 1024 = OK");
ASSERT(strcmp(buf, "1.00 KB") == 0, "1024 = '1.00 KB'");
s = exlib_format_size(1572864ULL, buf, sizeof(buf));
ASSERT(s == EXLIB_OK, "format 1.5MB = OK");
ASSERT(strcmp(buf, "1.50 MB") == 0, "1.5MB = '1.50 MB'");
s = exlib_format_size(100, buf, 5);
ASSERT(s == EXLIB_ERR_RANGE, "buffer kecil = ERR_RANGE");
}
static void test_contains(void) {
exlib_status s = exlib_contains("hello world", "world");
ASSERT(s == EXLIB_OK, "substring ditemukan");
s = exlib_contains("hello world", "xyz");
ASSERT(s == EXLIB_ERR_NOT_FOUND, "substring tidak ditemukan");
s = exlib_contains("abc", "");
ASSERT(s == EXLIB_OK, "needle kosong = OK");
s = exlib_contains(NULL, "x");
ASSERT(s == EXLIB_ERR_NULL, "NULL haystack = ERR_NULL");
}
int main(void) {
test_version();
test_mean();
test_stddev();
test_reverse_bytes();
test_format_size();
test_contains();
printf("\n=== Hasil Test examplelib ===\n");
printf("Total : %d\n", tests_run);
printf("Lulus : %d\n", tests_pass);
printf("Gagal : %d\n", tests_run - tests_pass);
return (tests_run == tests_pass) ? 0 : 1;
}Jalankan test (setelah bangun library):
gcc -std=c11 -Wall -Wextra -fPIC -Iinclude -c src/examplelib.c -o src/examplelib.o
ar rcs libexamplelib.a src/examplelib.o
gcc -std=c11 -Wall -Wextra -Iinclude -o test_examplelib \
tests/test_examplelib.c -L. -lexamplelib -lm
./test_examplelibOutput yang Dihasilkan:
=== Hasil Test examplelib ===
Total : 28
Lulus : 28
Gagal : 0Mari kita telaah baris-baris kode di atas:
- Baris 8-15 (
ASSERTmacro): Sama dengan proyek 7 dan 8. Patterndo { ... } while (0)memungkinkan macro dipakai seperti statement.__LINE__dicatat untuk debugging. - Baris 17-23 (
test_version): Smoke test paling dasar. Verifikasi string versi danstrerrormengembalikan pesan yang benar. Bila versi berubah (mis. ke 1.1.0), test ini perlu diupdate. - Baris 25-37 (
test_mean): Test happy path dengan 5 data, lalu dua kasus error (NULL dan n=0). Floating point comparison memakaifabsdengan epsilon1e-9untuk menghindari masalah presisi double. - Baris 39-46 (
test_stddev): Data2,4,4,4,5,5,7,9dipilih karena standar deviasi populasinya tepat 2.0 (variance = 4.0). Memakai dataset dengan hasil yang “bulat” memudahkan verifikasi tanpa kalkulator. - Baris 48-60 (
test_reverse_bytes): Test reverse pada array 5 elemen, lalu edge case: NULL input dan n=1. Bila n=1, tidak ada swap yang terjadi (loop whilei < jtidak dieksekusi). - Baris 62-77 (
test_format_size): Test 4 kasus: 0 bytes, 1024 (1 KB), 1572864 (1.5 MB), dan buffer terlalu kecil. Verifikasi tidak hanya kode return, tapi juga string output yang dihasilkan. - Baris 79-90 (
test_contains): Test substring ditemukan, tidak ditemukan, needle kosong (selalu match), dan NULL haystack. Empat kasus mencakup semua branch logika. - Baris 92-104 (
main): Panggil semua test function, lalu cetak ringkasan. Exit code 0 bila semua lulus, 1 bila ada yang gagal. Penting untuk CI/CD. - Build dengan library (
-L. -lexamplelib): Test di-link ke static library yang sudah dibangun. Pola ini memastikan test memakai library seperti yang akan dipakai pengguna nyata, bukan compile ulang dari source.
Kode Lengkap #
Kode lengkap untuk test_examplelib.c ada di direktori tests/ proyek. Total 28 assertion yang semuanya lulus.
Pengujian #
Verifikasi test memang menangkap bug dengan sengaja memodifikasi library:
# Skenario: hapus baris "if (data == NULL)" di exlib_mean
# Kompilasi ulang library + test
make clean && make test
# Test test_mean "mean(NULL) = ERR_NULL" harus FAIL
# Kembalikan kode, build ulang, test harus lulus lagiPengujian tambahan:
- Property-based test: generate random array dan verifikasi
exlib_meanselalu antara min dan max. - Stress test: array dengan 1 juta elemen, pastikan tidak ada overflow.
- Memory test dengan valgrind: pastikan tidak ada read/write out-of-bounds.
valgrind --leak-check=full --error-exitcode=1 ./test_examplelib