Assertion adalah pernyataan bahwa suatu kondisi harus benar pada titik tertentu dalam program. Bila kondisi ternyata salah, program segera berhenti dengan pesan diagnostik—mencegah eksekusi lanjut yang berpotensi merusak data. Dalam test, assertion adalah blok bangunan utama: setiap assert() yang lulus berarti satu asumsi terverifikasi. Pada chapter ini kita memakai assert() standar dari <assert.h>, lalu membangun macro asersi kustom yang mencatat informasi tambahan (pesan, nilai aktual, errno).
assert() Standar dari <assert.h> #
Macro assert(expr) memeriksa expr. Bila expr bernilai 0, assert mencetak Assertion failed: expr, file X, line N ke stderr lalu memanggil abort(). Header ini juga menghormati macro NDEBUG: bila NDEBUG didefinisikan saat compile (misalnya -DNDEBUG), seluruh assert menjadi no-op—berguna untuk build rilis tetapi berbahaya bila lupa di test.
/* File: divide.c — penggunaan assert sebagai precondition */
#include <assert.h>
#include <stdio.h>
static int divide(int a, int b) {
/* Precondition: b tidak boleh 0 — divalidasi dengan assert */
assert(b != 0 && "divide: b tidak boleh nol");
return a / b;
}
int main(void) {
int r = divide(20, 5);
printf("divide(20, 5) = %d\n", r);
return 0;
}Output yang Dihasilkan:
divide(20, 5) = 4String literal "divide: b tidak boleh nol" adalah trik umum—string yang tidak dipakai akan diabaikan compiler, tetapi muncul di pesan kegagalan assert. Saat divide(20, 0) dipanggil, outputnya menjadi:
Assertion failed: b != 0 && "divide: b tidak boleh nol", file divide.c, line 7
AbortedMacro Asersi Kustom #
Macro assert standar tidak menyertakan pesan kontekstual atau nilai aktual. Untuk test, kita sering ingin melihat expected vs actual atau membaca errno setelah syscall gagal. Berikut header macro kustom yang menyediakan empat varian asersi:
/* File: my_assert.h — macro asersi kustom */
#ifndef MY_ASSERT_H
#define MY_ASSERT_H
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
/* my_assert: seperti assert() standar, tetapi dengan pesan kustom */
#define my_assert(cond, msg) \
do { \
if (!(cond)) { \
fprintf(stderr, \
"Assertion failed: %s\n cond: (%s)\n file: %s:%d\n", \
(msg), #cond, __FILE__, __LINE__); \
abort(); \
} \
} while (0)
/* my_assert_eq: bandingkan dua int dengan pesan yang jelas */
#define my_assert_eq(a, b, msg) \
do { \
int _a = (a); int _b = (b); \
if (_a != _b) { \
fprintf(stderr, \
"Assertion failed: %s\n expected %d, got %d\n file: %s:%d\n", \
(msg), _b, _a, __FILE__, __LINE__); \
abort(); \
} \
} while (0)
/* my_assert_not_null */
#define my_assert_not_null(ptr, msg) \
do { \
if ((ptr) == NULL) { \
fprintf(stderr, \
"Assertion failed: %s (ptr is NULL)\n file: %s:%d\n", \
(msg), __FILE__, __LINE__); \
abort(); \
} \
} while (0)
/* my_assert_perror: asersi syscall + log errno */
#define my_assert_perror(rc, msg) \
do { \
if ((rc) != 0) { \
fprintf(stderr, \
"Assertion failed: %s (errno=%d: %s)\n file: %s:%d\n", \
(msg), errno, strerror(errno), __FILE__, __LINE__); \
abort(); \
} \
} while (0)
#endif /* MY_ASSERT_H */Menggunakan Macro Kustom #
Test berikut memakai keempat macro tersebut: assert() standar untuk precondition divide(), my_assert_eq untuk perbandingan numerik, my_assert_not_null untuk alokasi, dan my_assert_perror untuk verifikasi syscall fopen.
/* File: test_assertions.c — demonstrasi assert() standar dan macro kustom */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "my_assert.h"
static int divide(int a, int b) {
assert(b != 0 && "divide: b tidak boleh nol");
return a / b;
}
static int safe_alloc(int **out, size_t n) {
int *p = malloc(n * sizeof(int));
if (p == NULL) return -1;
*out = p;
return 0;
}
int main(void) {
/* 1) assert() standar dengan pesan */
int r = divide(20, 5);
printf("divide(20, 5) = %d\n", r);
/* 2) Macro kustom my_assert_eq */
int x = 3, y = 4;
int sum = x + y;
my_assert_eq(sum, 7, "penjumlahan 3+4 harusnya 7");
printf("my_assert_eq(sum, 7) lulus\n");
/* 3) Macro kustom my_assert_not_null */
int *arr = NULL;
int rc = safe_alloc(&arr, 10);
my_assert_eq(rc, 0, "safe_alloc seharusnya sukses");
my_assert_not_null(arr, "arr harusnya non-NULL setelah alloc");
arr[0] = 42;
printf("arr[0] = %d\n", arr[0]);
free(arr);
/* 4) Macro kustom my_assert_perror — verifikasi fopen sukses */
FILE *f = fopen("/tmp/assertions_demo_ok.txt", "w");
if (f == NULL) {
my_assert_perror(errno, "fopen seharusnya sukses untuk path /tmp valid");
} else {
fprintf(f, "demo\n");
fclose(f);
printf("fopen(/tmp/assertions_demo_ok.txt, w) sukses\n");
}
printf("Semua asersi lulus.\n");
return 0;
}Output yang Dihasilkan:
divide(20, 5) = 4
my_assert_eq(sum, 7) lulus
arr[0] = 42
fopen(/tmp/assertions_demo_ok.txt, w) sukses
Semua asersi lulus.Mari kita telaah baris-baris kunci di atas:
- Baris 9 (
assert(b != 0 && "...");):assertstandar menerima ekspresi boolean. Operator&&menggabungkan kondisi dengan string literal—string tidak pernah dievaluasi sebagai boolean, tetapi muncul di pesan kegagalan. Tanpa trik ini, pesanasserthanya berisi ekspresib != 0yang kurang informatif. - Macro
my_assert_eq(a, b, msg): Mengevaluasi(a)dan(b)ke variabel lokal_adan_b. Tanda kurung ganda pada(a)dan(b)melindungi dari operator precedence bila pemanggil menyertakan ekspresi kompleks (misalnyamy_assert_eq(x + 1, 4, ...)). Pesan kegagalan mencantumkan expected dan actual sehingga penyebab salah langsung terlihat tanpa debugging. - Macro
my_assert_not_null(ptr, msg): Khusus memeriksa pointerNULL. Memisahkan darimy_assert(ptr != NULL, ...)membuat pesan kegagalan lebih spesifik—pembaca tahu bahwa pointer adalah NULL, bukan sekadar “kondisi false”. - Macro
my_assert_perror(rc, msg): Menerima nilai kembalian syscall (biasanya0berarti sukses). Bilarc != 0, macro membacaerrnoglobal dan menerjemahkannya ke pesan denganstrerror. Trik ini menghasilkan output sepertierrno=2: No such file or directory—sangat membantu dibandingkan sekadar “fopen gagal”. - Baris 36 (
if (f == NULL) { my_assert_perror(errno, ...); } else { ... }): Kita memeriksaf == NULLdulu, lalu memanggil macro denganerrno. Macro akanabort()bilaerrno != 0. Pada contoh sukses di atas,fnon-NULL, jadi cabangelseyang dijalankan—macro tidak dipanggil.
Pelaporan Kegagalan yang Informatif #
Saat macro kustom gagal, output yang dihasilkan langsung menunjukkan masalah. Berikut program yang sengaja memanggil my_assert_eq(5, 10, ...):
/* File: demo_fail.c — demonstrasi my_assert_eq yang gagal */
#include <stdio.h>
#include "my_assert.h"
int main(void) {
int actual = 5;
int expected = 10;
my_assert_eq(actual, expected, "nilai harusnya 10");
return 0;
}Output yang Dihasilkan:
Assertion failed: nilai harusnya 10
expected 10, got 5
file demo_fail.c:8
AbortedPesan ini jauh lebih informatif daripada assert standar. Pengembang membaca baris pertama untuk konteks, baris kedua untuk selisih nilai, dan baris ketiga untuk lokasi kode. Tanpa pesan kontekstual, kita harus membuka file dan menelusuri logika untuk memahami mengapa assert gagal.
assert Produksi vs assert Test #
Penggunaan assert di kode produksi vs kode test memiliki pertimbangan berbeda:
- Kode produksi:
assertcocok untuk invariant internal yang seharusnya tidak pernah salah jika kode benar. Jangan pakaiassertuntuk validasi input dari pengguna atau sumber eksternal—gunakanif+ error handling. - Kode test:
assert(atau macro serupa) adalah alat utama. Setiap test pada dasarnya adalah rangkaianassert. Di test, kita ingin program berhenti pada kegagalan pertama agar tidak ada test berikutnya yang tercemar.