Blok test

Menulis test bernama yang ditemukan oleh test runner.

Daftar isi

Blok test adalah unit pengujian terkecil pada Zig, yaitu blok kode yang berisi pemeriksaan perilaku fungsi dan nilai. Topik ini membahas tempat berdirinya blok test, perbedaan pelaksanaannya terhadap program utama, aturan penamaan beserta urutan eksekusinya, serta bentuk test tanpa nama. Setelah topik ini dibaca, sekumpulan test pertama dapat ditulis dan dijalankan dengan perintah bawaan toolchain, tanpa pustaka pihak ketiga.

Konsep#

Blok test sebagai unit terkecil#

Blok test dideklarasikan dengan kata kunci test yang diikuti nama dalam tanda kutip beserta isi bloknya, misalnya test "penjumlahan" { ... }. Isinya berupa pernyataan biasa yang ditutup satu atau beberapa asersi (assertion), yaitu pemanggilan fungsi pemeriksa yang menghasilkan error apabila ekspektasinya tidak terpenuhi. Seluruh fungsi pemeriksa pustaka standar berada di dalam std.testing dan mengembalikan error, sehingga pemanggilannya hampir selalu didahului kata kunci try.

Tempat berdirinya blok test adalah tingkat container, yaitu langsung pada isi berkas, struct, atau enum. Blok test tidak dapat ditulis di dalam fungsi, sebagaimana deklarasi lain pada Zig. Penempatan pada tingkat container membuat test runner, yaitu bagian toolchain yang mengumpulkan dan menjalankan test, menemukan seluruh blok test secara otomatis tanpa pendaftaran manual. Nama berkas tidak perlu dicantumkan di mana pun karena test runner menjadikan nama container sebagai bagian dari identitas testnya.

Zig hanya mengompilasi blok test pada mode test, yaitu ketika perintah zig test dijalankan. Pada kompilasi biasa, blok test dibuang seluruhnya, sehingga test tidak menambah ukuran program akhir dan tidak memengaruhi jalannya program. Karakteristik ini mendorong test diletakkan dekat dengan kode yang diujinya, cukup pada berkas yang sama dengan fungsi yang diperiksanya.

Test pada berkas yang sama dengan program#

Satu berkas Zig boleh memuat program utama dan blok test sekaligus, dan keduanya menuntut perintah yang berbeda. Perintah zig run mengompilasi lalu mengeksekusi fungsi main, sedangkan blok test pada berkas yang sama dibiarkan tidak tersentuh. Sebaliknya, perintah zig test mengumpulkan dan menjalankan blok test tanpa memanggil main. Perilaku ganda ini memudahkan pengujian karena berkas contoh dapat menyertakan program pembandingnya.

Berkas catatan.zig berikut menghitung jumlah karakter yang bukan spasi, disertai dua test yang memeriksa fungsi hitungnya.

zig catatan.zig
const std = @import("std");

// menghitung jumlah karakter yang bukan spasi
pub fn jumlahKarakter(teks: []const u8) usize {
    var hitung: usize = 0;
    for (teks) |c| {
        if (c != ' ') hitung += 1;
    }
    return hitung;
}

pub fn main() void {
    std.debug.print("Jumlah karakter: {d}\n", .{jumlahKarakter("catatan harian")});
}

test "penghitungan karakter sederhana" {
    try std.testing.expect(jumlahKarakter("catatan harian") == 13);
}

test "teks tanpa spasi" {
    try std.testing.expect(jumlahKarakter("buku") == 4);
}
text Keluaran
Jumlah karakter: 13

Jalankan program tersebut dengan perintah berikut.

bash
# menjalankan program penghitung karakter
zig run catatan.zig
text Keluaran
Jumlah karakter: 13

Keluaran tersebut berasal sepenuhnya dari fungsi main, dan tidak ada satu test pun yang dijalankan. Kompilasi tetap berhasil meskipun kedua test di dalam berkas menggunakan std.testing, karena blok test memang tidak ikut dikompilasi pada mode biasa. Sekarang jalankan berkas yang sama dengan perintah zig test.

bash
# menguji fungsi penghitung karakter
zig test catatan.zig
text Keluaran
1/2 catatan.test.penghitungan karakter sederhana...OK
2/2 catatan.test.teks tanpa spasi...OK
All 2 tests passed.

Kali ini fungsi main tidak dieksekusi, sedangkan kedua test dijalankan satu per satu. Baris 1/2 dan 2/2 merupakan pelaporan kemajuan yang menyebut urutan test beserta namanya, dan akhiran OK menandakan ekspektasinya terpenuhi. Baris penutup All 2 tests passed. merangkum jumlah test yang lolos. Perintah zig test juga berakhir dengan kode keluar 0 pada keberhasilan, sehingga hasilnya dapat diperiksa oleh skrip.

Contoh Lengkap#

Test bernama dan urutan eksekusinya#

Nama test adalah string bebas yang ditentukan penulisnya, dan satu-satunya aturannya adalah menjelaskan perilaku yang diuji. Nama boleh memuat spasi dan huruf kecil, sehingga kalimat seperti penjumlahan melibatkan bilangan negatif dapat dituliskan apa adanya. Nama inilah yang tampil pada keluaran zig test, baik saat berhasil maupun saat gagal, sehingga test yang bermasalah dapat ditemukan tanpa membuka berkasnya terlebih dahulu.

Berkas hitung.zig berikut berisi satu fungsi tambah beserta dua test yang mengujinya. Berkas ini tidak memuat main, sehingga hanya dapat dijalankan melalui zig test.

zig hitung.zig
// tidak-dijalankan
const std = @import("std");

// menambahkan dua bilangan bulat bertanda
pub fn tambah(a: i32, b: i32) i32 {
    return a + b;
}

test "penjumlahan dua bilangan positif" {
    try std.testing.expect(tambah(3, 4) == 7);
}

test "penjumlahan melibatkan bilangan negatif" {
    try std.testing.expect(tambah(-2, 5) == 3);
}

Jalankan kumpulan test tersebut dengan perintah berikut.

bash
# menguji fungsi tambah beserta kedua kasusnya
zig test hitung.zig
text Keluaran
1/2 hitung.test.penjumlahan dua bilangan positif...OK
2/2 hitung.test.penjumlahan melibatkan bilangan negatif...OK
All 2 tests passed.

Setiap test muncul dengan identitas hitung.test.nama, yaitu nama berkas, kata test, kemudian nama testnya. Urutan eksekusinya mengikuti urutan deklarasi pada berkas, yaitu dari atas ke bawah, dan urutan ini tidak berubah antar eksekusi. Ketika salah satu test gagal, test setelahnya tetap dijalankan, sedangkan nama test yang gagal tampil pada laporan akhir beserta lokasi asersinya. Kepastian urutan dan penamaan ini membuat keluaran zig test dapat dibandingkan secara langsung antar versi kode.

Test tanpa nama#

Tanda kutip beserta namanya boleh ditiadakan, sehingga blok test dapat ditulis hanya sebagai test { ... }. Test tanpa nama tetap dikumpulkan dan dihitung oleh test runner, dan identitasnya pada keluaran berupa penomoran otomatis seperti test_0. Bentuk ini cocok untuk pemeriksaan pendek yang tidak perlu disebut namanya pada laporan, misalnya penjagaan sifat umum sebuah tipe.

zig Contoh: test tanpa nama
// tidak-dijalankan
test {
    try std.testing.expect("zig".len == 3);
}

Pemakaian yang paling lazim justru sebagai wadah penyambungan, yaitu memanggil fungsi tertentu satu kali sebelum test lain dijalankan. Bagian berikut memakai test tanpa nama untuk tujuan tersebut. Selain itu, campuran test bernama dan tanpa nama pada satu berkas sepenuhnya sah, dan keduanya dihitung bersama pada baris penutup All N tests passed.

Penjangkauan deklarasi yang tidak dirujuk#

Kompilator Zig menganalisis sebuah deklarasi hanya apabila deklarasi itu dirujuk. Sifat ini berlaku pula pada mode test, sehingga fungsi yang tidak pernah dipanggil oleh test mana pun tidak dianalisis, dan kesalahan kompilasi di dalamnya lolos tanpa terdeteksi. Untuk menutup celah tersebut, pustaka standar menyediakan std.testing.refAllDecls(@This()), yaitu fungsi yang merujuk seluruh deklarasi publik pada container yang diberikan.

zig Contoh: penjangkauan seluruh deklarasi publik
// tidak-dijalankan
test {
    std.testing.refAllDecls(@This());
}

Penempatan pada test tanpa nama seperti contoh di atas merupakan pola baku pemakaiannya. Setelah fungsi tersebut dipanggil, kesalahan kompilasi pada fungsi publik mana pun langsung terlihat ketika zig test dijalankan, bukan baru saat fungsi itu dipanggil dari kode produksi. Perlu dicatat bahwa penjangkauan ini hanya menjangkau deklarasi publik, sedangkan deklarasi privat tetap mengikuti aturan rujukan biasa.

Sifat container juga berlaku pada test yang berada di dalam struct. Test semacam itu baru dikumpulkan oleh test runner apabila structnya dirujuk, misalnya melalui pernyataan _ = KonverterSuhu; pada salah satu test ketika struct penampungnya bernama KonverterSuhu. Dengan rujukan tersebut, test pada tipe bentukan ikut terhitung meskipun tidak ada fungsi lain yang menggunakan tipenya.

Kesalahan umum#

Kesalahan pertama berupa menjalankan zig run lalu menyimpulkan pemeriksaan sudah berjalan. Perintah zig run hanya mengeksekusi main, sedangkan blok test hanya berjalan melalui zig test atau langkah test pada build.zig. Ketiadaan keluaran test pada zig run bukan tanda pemeriksaannya lulus, melainkan tanda pemeriksaannya tidak dijalankan.

Kesalahan kedua berupa menulis asersi tanpa try. Fungsi expectEqual mengembalikan error union, dan memanggilnya tanpa try membuat kegagalan tidak pernah diteruskan, sehingga test lulus meskipun harapannya salah. Pemanggilan try std.testing.expectEqual(...) adalah bentuk baku yang wajib dijaga.

Ringkasan#

  • Blok test ditulis dengan test "nama" { ... } pada tingkat container, hanya dikompilasi pada mode test, dan tidak boleh berada di dalam fungsi.
  • zig run mengeksekusi pub fn main dan mengabaikan blok test, sedangkan zig test menjalankan seluruh blok test tanpa memanggil main.
  • Nama test bebas ditentukan, tampil pada keluaran, dan urutan eksekusinya mengikuti urutan deklarasi dari atas ke bawah.
  • Test tanpa nama ditulis test { ... }, sedangkan std.testing.refAllDecls(@This()) memaksa seluruh deklarasi publik dianalisis pada mode test.

Referensi#