Pada bagian terakhir ini kita menulis tests.c dengan 21 test case yang mencakup seluruh operasi: tambah (sukses dan gagal), set status (sukses dan ID tidak ada), hapus dengan pergeseran, filter status, pencarian substring, dan round-trip persistensi dengan enum. Satu perintah make test membangun dan menjalankan seluruh rangkaian.

Konsep #

Test runner mengikuti pola yang sama dengan Proyek 03: macro PERIKSA mencatat lulus/gagal, counter global melacak total, dan fungsi main memulangkan 0 bila semua lulus atau 1 bila ada yang gagal. Yang berbeda: kita menambahkan test khusus untuk enum — memverifikasi bahwa status dan prioritas dipertahankan saat round-trip simpan-muat.

Test round-trip penting karena bug di salah satu modul dapat merusak yang lain. Sebagai contoh, bila storage_simpan keliru menulis (int)t->prioritas sebagai (int)t->status, test akan menangkap: status yang dimuat akan berbeda dari status yang disimpan. Tanpa test integrasi, bug seperti ini hanya muncul saat pengguna memakai aplikasi.

Test filter dan cari memverifikasi logika query. Filter diuji dengan tiga tugas yang berbeda status — kita pastikan hanya satu yang cocok dengan filter PROSES. Cari diuji dengan dua tugas yang mengandung substring “Belajar” — kita pastikan indeks pertama yang dikembalikan adalah 0.

Implementasi #

Test tambah dengan validasi:

c
static void test_tambah(void) {
    TaskList list;
    task_list_init(&list);
    TaskResult r1 = task_tambah(&list, "Belajar C", "Bab struct", TASK_PRIORITAS_TINGGI);
    TaskResult r2 = task_tambah(&list, "Belanja", "Beli susu", TASK_PRIORITAS_SEDANG);
    PERIKSA(r1 == TASK_OK && r2 == TASK_OK, "Tambah 2 tugas berhasil");
    PERIKSA(list.jumlah == 2, "Jumlah = 2");
    PERIKSA(list.data[0].id == 1, "ID pertama = 1");
    PERIKSA(list.data[0].status == TASK_STATUS_BELUM, "Status awal = BELUM");
}

Test round-trip dengan enum:

c
static void test_penyimpanan(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "T1", "D1", TASK_PRIORITAS_TINGGI);
    task_tambah(&list, "T2", "D2", TASK_PRIORITAS_RENDAH);
    task_set_status(&list, 2, TASK_STATUS_SELESAI);

    StorageStatus ss = storage_simpan(&list, "/tmp/test_tugas.txt");
    PERIKSA(ss == STORAGE_OK, "Simpan ke /tmp/test_tugas.txt");

    TaskList dimuat;
    task_list_init(&dimuat);
    ss = storage_muat(&dimuat, "/tmp/test_tugas.txt");
    PERIKSA(ss == STORAGE_OK, "Muat dari /tmp/test_tugas.txt");
    PERIKSA(dimuat.jumlah == 2, "Jumlah dimuat = 2");
    PERIKSA(strcmp(dimuat.data[0].judul, "T1") == 0, "Judul dimuat = 'T1'");
    PERIKSA(dimuat.data[1].status == TASK_STATUS_SELESAI, "Status dimuat = SELESAI");
    PERIKSA(dimuat.id_berikutnya == 3, "id_berikutnya = 3");
}

Kode Lengkap #

Berkas tests.c lengkap (21 test case):

c
#include "task.h"
#include "storage.h"

#include <stdio.h>
#include <string.h>

static int lulus = 0;
static int gagal = 0;

#define PERIKSA(kondisi, nama)                              \
    do {                                                    \
        if (kondisi) { lulus++; printf("[LULUS] %s\n", nama); } \
        else { gagal++; printf("[GAGAL] %s\n", nama); }     \
    } while (0)

static void test_tambah(void) {
    TaskList list;
    task_list_init(&list);
    TaskResult r1 = task_tambah(&list, "Belajar C", "Bab struct", TASK_PRIORITAS_TINGGI);
    TaskResult r2 = task_tambah(&list, "Belanja", "Beli susu", TASK_PRIORITAS_SEDANG);
    PERIKSA(r1 == TASK_OK && r2 == TASK_OK, "Tambah 2 tugas berhasil");
    PERIKSA(list.jumlah == 2, "Jumlah = 2");
    PERIKSA(list.data[0].id == 1, "ID pertama = 1");
    PERIKSA(list.data[0].status == TASK_STATUS_BELUM, "Status awal = BELUM");
}

static void test_tambah_invalid(void) {
    TaskList list;
    task_list_init(&list);
    PERIKSA(task_tambah(&list, "", "x", TASK_PRIORITAS_SEDANG) == TASK_ERR_INPUT_INVALID,
           "Judul kosong ditolak");
    PERIKSA(task_tambah(&list, "X", "Y", (TaskPrioritas)99) == TASK_ERR_INPUT_INVALID,
           "Prioritas invalid ditolak");
    PERIKSA(list.jumlah == 0, "Tidak ada tugas ditambahkan");
}

static void test_set_status(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "T1", "D1", TASK_PRIORITAS_SEDANG);
    TaskResult r = task_set_status(&list, 1, TASK_STATUS_SELESAI);
    PERIKSA(r == TASK_OK, "Set status SELESAI berhasil");
    PERIKSA(list.data[0].status == TASK_STATUS_SELESAI, "Status = SELESAI");
    PERIKSA(task_set_status(&list, 999, TASK_STATUS_PROSES) == TASK_ERR_TIDAK_DITEMUKAN,
           "Set status ID tidak ada -> error");
}

static void test_hapus(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "T1", "D1", TASK_PRIORITAS_SEDANG);
    task_tambah(&list, "T2", "D2", TASK_PRIORITAS_SEDANG);
    task_tambah(&list, "T3", "D3", TASK_PRIORITAS_SEDANG);
    TaskResult r = task_hapus(&list, 2);
    PERIKSA(r == TASK_OK && list.jumlah == 2, "Hapus id=2 berhasil");
    PERIKSA(strcmp(list.data[1].judul, "T3") == 0,
           "Setelah hapus, elemen 1 = 'T3'");
}

static void test_filter_status(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "T1", "D1", TASK_PRIORITAS_SEDANG);
    task_tambah(&list, "T2", "D2", TASK_PRIORITAS_SEDANG);
    task_tambah(&list, "T3", "D3", TASK_PRIORITAS_SEDANG);
    task_set_status(&list, 2, TASK_STATUS_PROSES);
    task_set_status(&list, 3, TASK_STATUS_SELESAI);
    int n = task_filter_status(&list, TASK_STATUS_PROSES, stdout);
    PERIKSA(n == 1, "Filter PROSES menemukan 1 tugas");
}

static void test_cari(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "Belajar C", "", TASK_PRIORITAS_SEDANG);
    task_tambah(&list, "Belajar Python", "", TASK_PRIORITAS_SEDANG);
    int idx = task_cari(&list, "Python");
    PERIKSA(idx == 1, "Cari 'Python' -> indeks 1");
    PERIKSA(task_cari(&list, "Rust") == -1, "Cari 'Rust' -> -1");
}

static void test_penyimpanan(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "T1", "D1", TASK_PRIORITAS_TINGGI);
    task_tambah(&list, "T2", "D2", TASK_PRIORITAS_RENDAH);
    task_set_status(&list, 2, TASK_STATUS_SELESAI);

    StorageStatus ss = storage_simpan(&list, "/tmp/test_tugas.txt");
    PERIKSA(ss == STORAGE_OK, "Simpan ke /tmp/test_tugas.txt");

    TaskList dimuat;
    task_list_init(&dimuat);
    ss = storage_muat(&dimuat, "/tmp/test_tugas.txt");
    PERIKSA(ss == STORAGE_OK, "Muat dari /tmp/test_tugas.txt");
    PERIKSA(dimuat.jumlah == 2, "Jumlah dimuat = 2");
    PERIKSA(strcmp(dimuat.data[0].judul, "T1") == 0, "Judul dimuat = 'T1'");
    PERIKSA(dimuat.data[1].status == TASK_STATUS_SELESAI, "Status dimuat = SELESAI");
    PERIKSA(dimuat.id_berikutnya == 3, "id_berikutnya = 3");
}

int main(void) {
    test_tambah();
    test_tambah_invalid();
    test_set_status();
    test_hapus();
    test_filter_status();
    test_cari();
    test_penyimpanan();
    printf("\nRingkasan: %d lulus, %d gagal\n", lulus, gagal);
    return gagal == 0 ? 0 : 1;
}

Pengujian #

Jalankan rangkaian test:

bash
make test

Output yang Dihasilkan:

text
[LULUS] Tambah 2 tugas berhasil
[LULUS] Jumlah = 2
[LULUS] ID pertama = 1
[LULUS] Status awal = BELUM
[LULUS] Judul kosong ditolak
[LULUS] Prioritas invalid ditolak
[LULUS] Tidak ada tugas ditambahkan
[LULUS] Set status SELESAI berhasil
[LULUS] Status = SELESAI
[LULUS] Set status ID tidak ada -> error
[LULUS] Hapus id=2 berhasil
[LULUS] Setelah hapus, elemen 1 = 'T3'
[LULUS] Filter PROSES menemukan 1 tugas
[LULUS] Cari 'Python' -> indeks 1
[LULUS] Cari 'Rust' -> -1
[LULUS] Simpan ke /tmp/test_tugas.txt
[LULUS] Muat dari /tmp/test_tugas.txt
[LULUS] Jumlah dimuat = 2
[LULUS] Judul dimuat = 'T1'
[LULUS] Status dimuat = SELESAI
[LULUS] id_berikutnya = 3

Ringkasan: 21 lulus, 0 gagal

Verifikasi exit code:

bash
./test_runner > /dev/null; echo $?

Output yang Dihasilkan:

text
0

Mari kita telaah komponen kunci pada tests.c:

  1. Baris task_tambah(&list, "Belajar C", "Bab struct", TASK_PRIORITAS_TINGGI): penggunaan enum literal sebagai parameter — compiler memeriksa tipe, tidak seperti integer mentah.
  2. Baris PERIKSA(list.data[0].status == TASK_STATUS_BELUM, ...): verifikasi status default. Bila task_tambah keliru tidak menginisialisasi status (mis. memset 0), test akan menangkap.
  3. Baris task_tambah(&list, "X", "Y", (TaskPrioritas)99): cast paksa integer 99 ke enum untuk menguji validasi. task_valid_prioritas harus menolaknya.
  4. Baris task_filter_status(&list, TASK_STATUS_PROSES, stdout) di test_filter_status: pemanggilan dengan stdout sebagai output. Test runner akan melihat output filter di konsol — boleh saja, tetapi pada test yang lebih ketat kita dapat memakai fmemopen untuk buffer in-memory.
  5. Baris PERIKSA(dimuat.data[1].status == TASK_STATUS_SELESAI, ...) di test_penyimpanan: test krusial yang memverifikasi enum dipertahankan. Bila storage_simpan menulis (int)t->status sebagai (int)t->prioritas, test ini akan menangkap.
  6. Baris PERIKSA(dimuat.id_berikutnya == 3, ...): invariannya counter ID. Bila storage_muat tidak meng-update id_berikutnya, kontak baru akan punya ID yang bentrok.

Proyek 04 selesai. Seluruh kode diuji dengan gcc -std=c11 -Wall -Wextra, dua puluh satu test LULUS, dan program interaktif berperilaku benar untuk tambah, tampil, filter, cari, set status, hapus, serta persistensi enum ke file. Selanjutnya kita lanjut ke Proyek 05 yang membahas pemroses file CSV.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

main.c

Unduh berkas
c
/* main.c -- titik masuk aplikasi pengelola tugas. */

#include "task.h"
#include "storage.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DB_PATH "tugas.txt"

static void baca_baris(const char *prompt, char *buf, size_t ukuran) {
    printf("%s", prompt);
    if (fgets(buf, (int)ukuran, stdin) == 0) {
        buf[0] = '\0';
        return;
    }
    size_t n = strlen(buf);
    while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) {
        buf[--n] = '\0';
    }
}

static int baca_int(const char *prompt, int *out) {
    char buf[32];
    baca_baris(prompt, buf, sizeof(buf));
    char *sisa = 0;
    long v = strtol(buf, &sisa, 10);
    if (sisa == buf) return 0;
    *out = (int)v;
    return 1;
}

static void menu_tambah(TaskList *list) {
    char judul[TASK_JUDUL_MAKS];
    char deskripsi[TASK_DESKRIPSI_MAKS];
    baca_baris("Judul      : ", judul, sizeof(judul));
    baca_baris("Deskripsi  : ", deskripsi, sizeof(deskripsi));
    int p = 0;
    if (!baca_int("Prioritas (1=Rendah,2=Sedang,3=Tinggi): ", &p)) {
        printf("Prioritas tidak valid.\n"); return;
    }
    TaskResult r = task_tambah(list, judul, deskripsi, (TaskPrioritas)p);
    if (r == TASK_OK) {
        printf("Tugas ditambahkan (id=%d).\n", list->data[list->jumlah - 1].id);
    } else {
        printf("Gagal menambah (kode %d).\n", r);
    }
}

static void menu_set_status(TaskList *list) {
    int id, s;
    if (!baca_int("ID tugas   : ", &id)) { printf("ID tidak valid.\n"); return; }
    if (!baca_int("Status (0=Belum,1=Proses,2=Selesai): ", &s)) {
        printf("Status tidak valid.\n"); return;
    }
    TaskResult r = task_set_status(list, id, (TaskStatus)s);
    if (r == TASK_OK) printf("Status diubah.\n");
    else if (r == TASK_ERR_TIDAK_DITEMUKAN) printf("Tugas tidak ditemukan.\n");
    else printf("Input tidak valid.\n");
}

static void menu_hapus(TaskList *list) {
    int id;
    if (!baca_int("ID tugas yang dihapus: ", &id)) { printf("ID tidak valid.\n"); return; }
    TaskResult r = task_hapus(list, id);
    if (r == TASK_OK) printf("Tugas dihapus.\n");
    else if (r == TASK_ERR_TIDAK_DITEMUKAN) printf("Tugas tidak ditemukan.\n");
}

static void menu_filter(const TaskList *list) {
    int s;
    if (!baca_int("Filter status (0=Belum,1=Proses,2=Selesai): ", &s)) {
        printf("Status tidak valid.\n"); return;
    }
    if (s < TASK_STATUS_BELUM || s > TASK_STATUS_SELESAI) {
        printf("Status tidak valid.\n"); return;
    }
    task_filter_status(list, (TaskStatus)s, stdout);
}

static void menu_cari(const TaskList *list) {
    char kata[TASK_JUDUL_MAKS];
    baca_baris("Kata kunci judul: ", kata, sizeof(kata));
    int idx = task_cari(list, kata);
    if (idx < 0) {
        printf("Tidak ada tugas dengan judul mengandung '%s'.\n", kata);
    } else {
        const Task *t = &list->data[idx];
        printf("Ditemukan: [%d] %s | %s | %s\n",
               t->id, t->judul,
               task_status_nama(t->status),
               task_prioritas_nama(t->prioritas));
    }
}

int main(void) {
    TaskList list;
    task_list_init(&list);

    StorageStatus sm = storage_muat(&list, DB_PATH);
    if (sm == STORAGE_OK) {
        printf("Memuat %d tugas dari %s.\n", list.jumlah, DB_PATH);
    } else if (sm == STORAGE_ERR_BUKA_FILE) {
        printf("Belum ada file %s. Memulai dengan daftar kosong.\n", DB_PATH);
    } else {
        printf("Peringatan: format file rusak (kode %d).\n", sm);
    }

    int lanjut = 1;
    while (lanjut) {
        printf("\n=== Pengelola Tugas ===\n");
        printf("1. Tambah\n2. Tampilkan\n3. Filter status\n");
        printf("4. Cari judul\n5. Set status\n6. Hapus\n7. Keluar\n");
        int pilihan = 0;
        if (!baca_int("Pilih: ", &pilihan)) { printf("Pilihan tidak valid.\n"); continue; }

        switch (pilihan) {
            case 1: menu_tambah(&list); break;
            case 2: task_tampilkan(&list, stdout); break;
            case 3: menu_filter(&list); break;
            case 4: menu_cari(&list); break;
            case 5: menu_set_status(&list); break;
            case 6: menu_hapus(&list); break;
            case 7: lanjut = 0; break;
            default: printf("Pilihan tidak dikenal.\n"); break;
        }
    }

    StorageStatus ss = storage_simpan(&list, DB_PATH);
    if (ss == STORAGE_OK) printf("Menyimpan %d tugas ke %s.\n", list.jumlah, DB_PATH);
    else printf("Gagal menyimpan (kode %d).\n", ss);
    printf("Terima kasih.\n");
    return 0;
}

storage.c

Unduh berkas
c
/* storage.c -- persistensi tugas ke file teks. */

#include "storage.h"

#include <stdio.h>
#include <string.h>

static void potong_newline(char *s) {
    size_t n = strlen(s);
    while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) {
        s[--n] = '\0';
    }
}

StorageStatus storage_simpan(const TaskList *list, const char *path) {
    if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
    FILE *f = fopen(path, "w");
    if (f == 0) return STORAGE_ERR_BUKA_FILE;

    fprintf(f, "%d\n", list->jumlah);
    for (int i = 0; i < list->jumlah; i++) {
        const Task *t = &list->data[i];
        /* Ganti | di judul/deskripsi dengan spasi agar parsing tetap aman. */
        fprintf(f, "%d|%s|%s|%d|%d\n",
                t->id, t->judul, t->deskripsi, (int)t->status, (int)t->prioritas);
    }

    if (fclose(f) != 0) return STORAGE_ERR_TULIS;
    return STORAGE_OK;
}

StorageStatus storage_muat(TaskList *list, const char *path) {
    if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
    FILE *f = fopen(path, "r");
    if (f == 0) return STORAGE_ERR_BUKA_FILE;

    int jumlah = 0;
    if (fscanf(f, "%d\n", &jumlah) != 1) {
        fclose(f);
        return STORAGE_ERR_FORMAT;
    }
    if (jumlah < 0 || jumlah > TASK_MAKS) {
        fclose(f);
        return STORAGE_ERR_FORMAT;
    }

    char baris[512];
    int id_terbesar = list->id_berikutnya - 1;

    for (int i = 0; i < jumlah; i++) {
        if (fgets(baris, sizeof(baris), f) == 0) {
            fclose(f);
            return STORAGE_ERR_FORMAT;
        }
        potong_newline(baris);

        int id, status, prioritas;
        char judul[TASK_JUDUL_MAKS];
        char deskripsi[TASK_DESKRIPSI_MAKS];

        int parsed = sscanf(baris, "%d|%79[^|]|%199[^|]|%d|%d",
                            &id, judul, deskripsi, &status, &prioritas);
        if (parsed < 5) {
            fclose(f);
            return STORAGE_ERR_FORMAT;
        }
        if (list->jumlah >= TASK_MAKS) break;

        Task *t = &list->data[list->jumlah];
        t->id = id;
        strncpy(t->judul, judul, TASK_JUDUL_MAKS - 1);
        t->judul[TASK_JUDUL_MAKS - 1] = '\0';
        strncpy(t->deskripsi, deskripsi, TASK_DESKRIPSI_MAKS - 1);
        t->deskripsi[TASK_DESKRIPSI_MAKS - 1] = '\0';
        t->status = (TaskStatus)status;
        t->prioritas = (TaskPrioritas)prioritas;
        list->jumlah++;

        if (id > id_terbesar) id_terbesar = id;
    }

    list->id_berikutnya = id_terbesar + 1;
    fclose(f);
    return STORAGE_OK;
}

task.c

Unduh berkas
c
/* task.c -- implementasi model dan operasi tugas. */

#include "task.h"

#include <string.h>
#include <stdio.h>

void task_list_init(TaskList *list) {
    if (list == 0) return;
    list->jumlah = 0;
    list->id_berikutnya = 1;
}

int task_valid_judul(const char *judul) {
    if (judul == 0 || judul[0] == '\0') return 0;
    if (strlen(judul) >= TASK_JUDUL_MAKS) return 0;
    return 1;
}

int task_valid_prioritas(int p) {
    return p == TASK_PRIORITAS_RENDAH
        || p == TASK_PRIORITAS_SEDANG
        || p == TASK_PRIORITAS_TINGGI;
}

TaskResult task_tambah(TaskList *list,
                       const char *judul,
                       const char *deskripsi,
                       TaskPrioritas prioritas) {
    if (list == 0) return TASK_ERR_INPUT_INVALID;
    if (!task_valid_judul(judul)) return TASK_ERR_INPUT_INVALID;
    if (!task_valid_prioritas((int)prioritas)) return TASK_ERR_INPUT_INVALID;
    if (list->jumlah >= TASK_MAKS) return TASK_ERR_PENUH;

    Task *t = &list->data[list->jumlah];
    t->id = list->id_berikutnya++;
    strncpy(t->judul, judul, TASK_JUDUL_MAKS - 1);
    t->judul[TASK_JUDUL_MAKS - 1] = '\0';
    strncpy(t->deskripsi, deskripsi ? deskripsi : "", TASK_DESKRIPSI_MAKS - 1);
    t->deskripsi[TASK_DESKRIPSI_MAKS - 1] = '\0';
    t->status = TASK_STATUS_BELUM;
    t->prioritas = prioritas;
    list->jumlah++;
    return TASK_OK;
}

void task_tampilkan(const TaskList *list, FILE *out) {
    if (list == 0 || out == 0) return;
    fprintf(out, "%-4s %-30s %-12s %-10s\n",
            "ID", "Judul", "Status", "Prioritas");
    for (int i = 0; i < list->jumlah; i++) {
        const Task *t = &list->data[i];
        fprintf(out, "%-4d %-30s %-12s %-10s\n",
                t->id, t->judul,
                task_status_nama(t->status),
                task_prioritas_nama(t->prioritas));
    }
    fprintf(out, "Total: %d tugas\n", list->jumlah);
}

TaskResult task_set_status(TaskList *list, int id, TaskStatus status) {
    if (list == 0) return TASK_ERR_INPUT_INVALID;
    if (status < TASK_STATUS_BELUM || status > TASK_STATUS_SELESAI) {
        return TASK_ERR_INPUT_INVALID;
    }
    for (int i = 0; i < list->jumlah; i++) {
        if (list->data[i].id == id) {
            list->data[i].status = status;
            return TASK_OK;
        }
    }
    return TASK_ERR_TIDAK_DITEMUKAN;
}

TaskResult task_hapus(TaskList *list, int id) {
    if (list == 0) return TASK_ERR_INPUT_INVALID;
    for (int i = 0; i < list->jumlah; i++) {
        if (list->data[i].id == id) {
            for (int j = i; j < list->jumlah - 1; j++) {
                list->data[j] = list->data[j + 1];
            }
            list->jumlah--;
            return TASK_OK;
        }
    }
    return TASK_ERR_TIDAK_DITEMUKAN;
}

int task_filter_status(const TaskList *list, TaskStatus status, FILE *out) {
    if (list == 0 || out == 0) return 0;
    int count = 0;
    fprintf(out, "%-4s %-30s %-12s %-10s\n",
            "ID", "Judul", "Status", "Prioritas");
    for (int i = 0; i < list->jumlah; i++) {
        const Task *t = &list->data[i];
        if (t->status == status) {
            fprintf(out, "%-4d %-30s %-12s %-10s\n",
                    t->id, t->judul,
                    task_status_nama(t->status),
                    task_prioritas_nama(t->prioritas));
            count++;
        }
    }
    fprintf(out, "Ditemukan: %d tugas\n", count);
    return count;
}

int task_cari(const TaskList *list, const char *kata_kunci) {
    if (list == 0 || kata_kunci == 0) return -1;
    for (int i = 0; i < list->jumlah; i++) {
        if (strstr(list->data[i].judul, kata_kunci) != 0) {
            return i;
        }
    }
    return -1;
}

const char *task_status_nama(TaskStatus s) {
    switch (s) {
        case TASK_STATUS_BELUM:  return "Belum";
        case TASK_STATUS_PROSES: return "Proses";
        case TASK_STATUS_SELESAI:return "Selesai";
        default:                 return "?";
    }
}

const char *task_prioritas_nama(TaskPrioritas p) {
    switch (p) {
        case TASK_PRIORITAS_RENDAH: return "Rendah";
        case TASK_PRIORITAS_SEDANG: return "Sedang";
        case TASK_PRIORITAS_TINGGI: return "Tinggi";
        default:                     return "?";
    }
}

tests.c

Unduh berkas
c
/* tests.c -- rangkaian test untuk modul task dan storage. */

#include "task.h"
#include "storage.h"

#include <stdio.h>
#include <string.h>

static int lulus = 0;
static int gagal = 0;

#define PERIKSA(kondisi, nama)                              \
    do {                                                    \
        if (kondisi) { lulus++; printf("[LULUS] %s\n", nama); } \
        else { gagal++; printf("[GAGAL] %s\n", nama); }     \
    } while (0)

static void test_tambah(void) {
    TaskList list;
    task_list_init(&list);
    TaskResult r1 = task_tambah(&list, "Belajar C", "Bab struct", TASK_PRIORITAS_TINGGI);
    TaskResult r2 = task_tambah(&list, "Belanja", "Beli susu", TASK_PRIORITAS_SEDANG);
    PERIKSA(r1 == TASK_OK && r2 == TASK_OK, "Tambah 2 tugas berhasil");
    PERIKSA(list.jumlah == 2, "Jumlah = 2");
    PERIKSA(list.data[0].id == 1, "ID pertama = 1");
    PERIKSA(list.data[0].status == TASK_STATUS_BELUM, "Status awal = BELUM");
}

static void test_tambah_invalid(void) {
    TaskList list;
    task_list_init(&list);
    PERIKSA(task_tambah(&list, "", "x", TASK_PRIORITAS_SEDANG) == TASK_ERR_INPUT_INVALID,
           "Judul kosong ditolak");
    PERIKSA(task_tambah(&list, "X", "Y", (TaskPrioritas)99) == TASK_ERR_INPUT_INVALID,
           "Prioritas invalid ditolak");
    PERIKSA(list.jumlah == 0, "Tidak ada tugas ditambahkan");
}

static void test_set_status(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "T1", "D1", TASK_PRIORITAS_SEDANG);
    TaskResult r = task_set_status(&list, 1, TASK_STATUS_SELESAI);
    PERIKSA(r == TASK_OK, "Set status SELESAI berhasil");
    PERIKSA(list.data[0].status == TASK_STATUS_SELESAI, "Status = SELESAI");
    PERIKSA(task_set_status(&list, 999, TASK_STATUS_PROSES) == TASK_ERR_TIDAK_DITEMUKAN,
           "Set status ID tidak ada -> error");
}

static void test_hapus(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "T1", "D1", TASK_PRIORITAS_SEDANG);
    task_tambah(&list, "T2", "D2", TASK_PRIORITAS_SEDANG);
    task_tambah(&list, "T3", "D3", TASK_PRIORITAS_SEDANG);
    TaskResult r = task_hapus(&list, 2);
    PERIKSA(r == TASK_OK && list.jumlah == 2, "Hapus id=2 berhasil");
    PERIKSA(strcmp(list.data[1].judul, "T3") == 0,
           "Setelah hapus, elemen 1 = 'T3'");
}

static void test_filter_status(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "T1", "D1", TASK_PRIORITAS_SEDANG);   /* BELUM */
    task_tambah(&list, "T2", "D2", TASK_PRIORITAS_SEDANG);
    task_tambah(&list, "T3", "D3", TASK_PRIORITAS_SEDANG);
    task_set_status(&list, 2, TASK_STATUS_PROSES);
    task_set_status(&list, 3, TASK_STATUS_SELESAI);

    int n = task_filter_status(&list, TASK_STATUS_PROSES, stdout);
    PERIKSA(n == 1, "Filter PROSES menemukan 1 tugas");
}

static void test_cari(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "Belajar C", "", TASK_PRIORITAS_SEDANG);
    task_tambah(&list, "Belajar Python", "", TASK_PRIORITAS_SEDANG);
    int idx = task_cari(&list, "Python");
    PERIKSA(idx == 1, "Cari 'Python' -> indeks 1");
    PERIKSA(task_cari(&list, "Rust") == -1, "Cari 'Rust' -> -1");
}

static void test_penyimpanan(void) {
    TaskList list;
    task_list_init(&list);
    task_tambah(&list, "T1", "D1", TASK_PRIORITAS_TINGGI);
    task_tambah(&list, "T2", "D2", TASK_PRIORITAS_RENDAH);
    task_set_status(&list, 2, TASK_STATUS_SELESAI);

    StorageStatus ss = storage_simpan(&list, "/tmp/test_tugas.txt");
    PERIKSA(ss == STORAGE_OK, "Simpan ke /tmp/test_tugas.txt");

    TaskList dimuat;
    task_list_init(&dimuat);
    ss = storage_muat(&dimuat, "/tmp/test_tugas.txt");
    PERIKSA(ss == STORAGE_OK, "Muat dari /tmp/test_tugas.txt");
    PERIKSA(dimuat.jumlah == 2, "Jumlah dimuat = 2");
    PERIKSA(strcmp(dimuat.data[0].judul, "T1") == 0, "Judul dimuat = 'T1'");
    PERIKSA(dimuat.data[1].status == TASK_STATUS_SELESAI, "Status dimuat = SELESAI");
    PERIKSA(dimuat.id_berikutnya == 3, "id_berikutnya = 3");
}

int main(void) {
    test_tambah();
    test_tambah_invalid();
    test_set_status();
    test_hapus();
    test_filter_status();
    test_cari();
    test_penyimpanan();
    printf("\nRingkasan: %d lulus, %d gagal\n", lulus, gagal);
    return gagal == 0 ? 0 : 1;
}