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.

c
/* 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:

text
divide(20, 5) = 4

String 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:

text
Assertion failed: b != 0 && "divide: b tidak boleh nol", file divide.c, line 7
Aborted

Macro 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:

c
/* 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.

c
/* 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:

text
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:

  1. Baris 9 (assert(b != 0 && "...");): assert standar menerima ekspresi boolean. Operator && menggabungkan kondisi dengan string literal—string tidak pernah dievaluasi sebagai boolean, tetapi muncul di pesan kegagalan. Tanpa trik ini, pesan assert hanya berisi ekspresi b != 0 yang kurang informatif.
  2. Macro my_assert_eq(a, b, msg): Mengevaluasi (a) dan (b) ke variabel lokal _a dan _b. Tanda kurung ganda pada (a) dan (b) melindungi dari operator precedence bila pemanggil menyertakan ekspresi kompleks (misalnya my_assert_eq(x + 1, 4, ...)). Pesan kegagalan mencantumkan expected dan actual sehingga penyebab salah langsung terlihat tanpa debugging.
  3. Macro my_assert_not_null(ptr, msg): Khusus memeriksa pointer NULL. Memisahkan dari my_assert(ptr != NULL, ...) membuat pesan kegagalan lebih spesifik—pembaca tahu bahwa pointer adalah NULL, bukan sekadar “kondisi false”.
  4. Macro my_assert_perror(rc, msg): Menerima nilai kembalian syscall (biasanya 0 berarti sukses). Bila rc != 0, macro membaca errno global dan menerjemahkannya ke pesan dengan strerror. Trik ini menghasilkan output seperti errno=2: No such file or directory—sangat membantu dibandingkan sekadar “fopen gagal”.
  5. Baris 36 (if (f == NULL) { my_assert_perror(errno, ...); } else { ... }): Kita memeriksa f == NULL dulu, lalu memanggil macro dengan errno. Macro akan abort() bila errno != 0. Pada contoh sukses di atas, f non-NULL, jadi cabang else yang 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, ...):

c
/* 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:

text
Assertion failed: nilai harusnya 10
  expected 10, got 5
  file demo_fail.c:8
Aborted

Pesan 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: assert cocok untuk invariant internal yang seharusnya tidak pernah salah jika kode benar. Jangan pakai assert untuk validasi input dari pengguna atau sumber eksternal—gunakan if + error handling.
  • Kode test: assert (atau macro serupa) adalah alat utama. Setiap test pada dasarnya adalah rangkaian assert. Di test, kita ingin program berhenti pada kegagalan pertama agar tidak ada test berikutnya yang tercemar.