Selama section-section sebelumnya, cara kita memeriksa perilaku kode selalu sama: jalankan berkas, baca keluaran console.log, lalu cocokkan dengan yang diharapkan. Cara itu berfungsi, tetapi pemeriksaannya ada di mata kita. Setiap kali kode berubah, seseorang harus mengulang proses itu — dan kelalaian satu langkah cukup untuk melewatkan angka yang salah.
Unit test memindahkan pemeriksaan itu dari mata ke kode. Kita menulis fungsi uji yang memanggil unit terkecil — satu fungsi, satu method — dan mengklaim hasilnya dengan asersi. Chapter ini memakai test runner bawaan Node.js bernama node:test: tanpa instalasi, tanpa konfigurasi, cukup berkas .mjs dan satu perintah.
Menulis Uji Pertama dengan test() #
Unit yang diuji di chapter ini adalah totalKeranjang, fungsi penghitung nilai keranjang belanja. Berkas ujinya sekaligus memperkenalkan dua bentuk penulisan yang didukung node:test: fungsi test() berdiri sendiri, dan pasangan describe/it yang mengelompokkan uji:
import { test, describe, it } from 'node:test';
import assert from 'node:assert/strict';
// Unit yang diuji: fungsi penghitung total keranjang.
function totalKeranjang(item) {
return item.reduce((total, baris) => total + baris.harga * baris.jumlah, 0);
}
test('totalKeranjang menjumlahkan harga dikali jumlah', () => {
const item = [
{ nama: 'Buku Algoritma', harga: 85000, jumlah: 2 },
{ nama: 'Pulpen Gel', harga: 12000, jumlah: 3 },
];
assert.strictEqual(totalKeranjang(item), 206000);
});
describe('totalKeranjang', () => {
it('mengembalikan nol untuk keranjang kosong', () => {
assert.strictEqual(totalKeranjang([]), 0);
});
it('menghitung satu baris sebagai harga kali jumlah', () => {
const item = [{ nama: 'Buku Tulis', harga: 5000, jumlah: 4 }];
assert.strictEqual(totalKeranjang(item), 20000);
});
});Mari kita telaah baris-baris pentingnya:
- Baris 1 (
import { test, describe, it }): ketiga fungsi pendaftar uji datang darinode:test.testdanitidentik perilakunya —ithanyalah gaya penamaan yang populer di ekosistem pengujian. - Baris 2 (
import assert ...): assertion librarynode:assertmodestrictyang sudah kita kenal di chapter sebelumnya. - Baris 5–7 (
function totalKeranjang): unit yang diuji. Ia murni — menerima data, mengembalikan angka, tanpa menyentuh berkas atau jaringan. Unit murni seperti inilah kandidat uji unit terbaik. - Baris 9 (
test('...', () => {...})): satu uji bernama. Nama uji ditulis sebagai kalimat pernyataan: “totalKeranjang menjumlahkan harga dikali jumlah”. Bila nanti uji ini gagal, laporan runner sudah menceritakan masalahnya tanpa perlu membuka berkas. - Baris 10–13 (data uji): data ditulis langsung di dalam uji, sekecil mungkin, dan hanya memuat properti yang benar-benar dipakai fungsi (
namadisertakan agar bentuk data realistis). - Baris 15 (
assert.strictEqual(...)): klaim hasil.85000 × 2 + 12000 × 3harus tepat206000. - Baris 18–27 (
describe/it):describe('totalKeranjang')membentuk grup bernama; duaitdi dalamnya memuat kasus batas — keranjang kosong dan keranjang satu baris. Grup membuat laporan runner terbaca berdasarkan unit yang diuji.
Menjalankan Uji dengan node –test #
Simpan berkas uji di sebuah folder, lalu jalankan test runner dengan menyebut nama berkasnya:
node --test uji-keranjang.test.mjsOutput yang Dihasilkan:
✔ totalKeranjang menjumlahkan harga dikali jumlah (0.639933ms)
▶ totalKeranjang
✔ mengembalikan nol untuk keranjang kosong (0.150623ms)
✔ menghitung satu baris sebagai harga kali jumlah (0.101612ms)
✔ totalKeranjang (0.51342ms)
ℹ tests 3
ℹ suites 1
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 55.136702Bentuk keluaran ini disebut spec reporter — pelapor bawaan Node.js 24 yang memakai simbol agar hasil cepat discan:
✔menandai uji yang lulus; uji yang gagal ditandai✖beserta pesan asersinya.▶ totalKeranjangadalah grup daridescribe; duaitdi dalamnya menjorok satu tingkat, lalu grupnya ditutup dengan baris ringkasannya sendiri.- Baris-baris
ℹadalah ringkasan akhir:tests 3menghitung semua uji (yang berdiri sendiri maupun di dalam grup),suites 1menghitung grup, danduration_msmencatat total waktu.
Angka durasi — baik 0.639933ms per uji maupun 55.136702ms total — akan berbeda setiap kali dijalankan di mesin Anda; yang stabil adalah urutan dan nama ujinya.
Bila ingin keluaran format lain, runner menerima opsi --test-reporter. Format TAP (Test Anything Protocol) adalah format teks sederhana yang lama dipakai alat-alat uji, sehingga mudah dibaca program lain seperti server CI (continuous integration):
node --test --test-reporter tap uji-keranjang.test.mjsOutput yang Dihasilkan:
TAP version 13
# Subtest: totalKeranjang menjumlahkan harga dikali jumlah
ok 1 - totalKeranjang menjumlahkan harga dikali jumlah
---
duration_ms: 0.723448
type: 'test'
...
# Subtest: totalKeranjang
# Subtest: mengembalikan nol untuk keranjang kosong
ok 1 - mengembalikan nol untuk keranjang kosong
---
duration_ms: 0.171846
type: 'test'
...
# Subtest: menghitung satu baris sebagai harga kali jumlah
ok 2 - menghitung satu baris sebagai harga kali jumlah
---
duration_ms: 0.126126
type: 'test'
...
1..2
ok 2 - totalKeranjang
---
duration_ms: 0.539935
type: 'suite'
...
1..2
# tests 3
# suites 1
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 55.758158TAP menyebut setiap uji dengan nomor urut dan status ok, sementara ringkasannya diawali tanda #. Isinya sama persis dengan spec reporter — hanya format penyajiannya yang berbeda.
Saat uji bertambah banyak, menjalankan semuanya setiap kali menyimpan berkas menjadi boros. Opsi --test-name-pattern menyaring uji berdasarkan potongan namanya — misalnya hanya uji yang mengandung kata “kosong”:
node --test --test-name-pattern "kosong" uji-keranjang.test.mjsOutput yang Dihasilkan:
▶ totalKeranjang
✔ mengembalikan nol untuk keranjang kosong (0.393329ms)
✔ totalKeranjang (1.172553ms)
ℹ tests 1
ℹ suites 1
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56.783556Dua uji lainnya tidak dijalankan sama sekali — ringkasan tests 1 menghitung hanya yang lolos pola. Saringan ini menerima RegExp penuh, sehingga pola seperti "kosong|satu baris" juga sah; gunakan saat berfokus pada satu area, dan jalankan rangkaian penuh sebelum mendorong kode.
Apa yang Terjadi di Balik Layar #
Ketika node --test uji-keranjang.test.mjs dijalankan, runner membaca berkas, menjumpai tiga pemanggilan test()/it(), lalu mendaftarkannya sebagai daftar subtes. Setiap uji dieksekusi satu per satu; asersi di dalamnya yang menentukan status akhirnya. Bila sebuah uji melempar galat — misalnya AssertionError — runner menandainya gagal, mengumpulkan pesannya, dan tetap melanjutkan uji lain. Kegagalan satu kasus tidak membawa jatuh kasus lainnya.
Runner juga merawat pemisahan antar-berkas: setiap berkas uji dijalankan dalam proses Node terpisah, sehingga variabel di satu berkas tidak bisa mengganggu berkas lain. Di dalam satu berkas, urutan uji mengikuti urutan penulisan — karena itu uji unit sebaiknya tidak saling bergantung pada data dari uji sebelumnya.
Ringkasan akhir juga punya tempat untuk status di luar lulus-gagal. test.skip('nama', fn) mendaftarkan uji yang sengaja dilewati — misalnya untuk kasus yang bergantung pada fitur yang sedang dikerjakan — dan test.todo('nama') hanya mencatat rencana tanpa badan fungsi sama sekali. Keduanya muncul di laporan dengan penanda # SKIP dan # TODO serta dihitung pada baris skipped dan todo di ringkasan, sehingga niat yang tertunda tetap terlihat dan tidak berubah menjadi uji yang dilupakan. Sementara cancelled menandai uji yang dihentikan di tengah jalan — jarang terjadi, misalnya saat runner dimatikan sebelum selesai.
Ada satu catatan praktis lagi: karena node:test terpasang di dalam Node itu sendiri, berkas uji juga bisa dijalankan langsung dengan node uji-keranjang.test.mjs tanpa flag --test. Semua uji tetap tereksekusi dan laporannya tetap keluar. Flag --test diperlukan saat kita ingin runner yang mencari berkas sendiri atau menggabungkan laporan banyak berkas.
Sampai di sini setiap uji kita selalu lulus. Chapter berikutnya membedah alat penentu lulus-gagalnya itu: keluarga fungsi assert beserta bentuk pesan kegagalan yang mereka hasilkan.