Aplikasi yang berjalan sekali dengan benar belum tentu tetap benar besok. Setiap perbaikan fitur lama, penambahan fitur baru, atau pembaruan dependency berpotensi merusak perilaku yang sebelumnya berjalan lancar — dan kerusakan semacam itu sering baru terasa saat aplikasi sudah dipakai pengguna. Testing adalah praktik menulis kode yang memeriksa kode lain secara otomatis, sehingga setiap perubahan dapat diperiksa ulang dalam hitungan detik tanpa mengeklik aplikasi secara manual. Pada Section ini kita membangun kebiasaan testing dari nol, dimulai dari konsep dasarnya: mengapa kita menulis test, jenis-jenis pengujian, dan anatomi sebuah test case yang sehat.

Mengapa Kita Menulis Test #

Alasan paling praktis untuk menulis test adalah perlindungan dari regresi — kesalahan yang muncul kembali setelah kode yang tadinya benar diubah. Bayangkan sebuah function hitungDiskon yang dipakai di dua layar aplikasi; ketika kita menambahkan aturan diskon khusus, test yang sudah ada akan langsung memberi tahu jika perhitungan pada layar lain ikut berubah. Tanpa test, kita hanya bisa berharap tidak ada yang terlewat saat memeriksa manual.

Manfaat testing tidak berhenti pada pencegahan regresi. Sebuah suite test yang rapi juga berfungsi sebagai dokumentasi hidup: membaca daftar nama test memberi gambaran perilaku apa saja yang dijamin oleh kode. Test juga memberi kita keberanian untuk melakukan refactoring — mengubah struktur internal kode tanpa mengubah perilakunya — karena kegagalan akan tertangkap sebelum mencapai pengguna. Terakhir, kode yang mudah diuji cenderung lebih terstruktur: function yang panjang dan melakukan banyak hal sekaligus sulit diuji, sehingga keinginan menulis test mendorong kita memecah kode menjadi bagian-bagian kecil yang jelas tugasnya.

Piramida Testing #

Tidak semua test dibuat sama. Sebagian berjalan dalam sepersekian milidetik dan memeriksa satu function; sebagian lain menjalankan aplikasi utuh dan menyalin alur kerja pengguna. Untuk memetakan pilihan tersebut, komunitas memakai gambaran piramida testing: banyak test cepat di dasar, sedikit test lambat di puncak.

JenisCakupanKecepatanContoh
Unit testSatu function atau satu moduleSangat cepatMenguji hitungDiskon(150000, 20) menghasilkan 120000
Integration testBeberapa komponen yang bekerja samaSedangMenguji HTTP server beserta handler dan parser JSON-nya
End-to-end testAplikasi utuh dari sudut pandang penggunaLambatMembuka aplikasi lewat browser sungguhan lalu mengklik tombol beli

Mengapa unit test ditempatkan paling banyak? Karena unit test murah: cepat dijalankan, mudah ditulis, dan menunjuk letak kesalahan dengan presisi — nama test yang gagal langsung menyebut function mana yang bermasalah. Sebaliknya, end-to-end test mahal per satuan: lambat, rapuh terhadap perubahan tampilan, dan saat gagal kita harus menyelidiki seluruh tumpukan untuk menemukan penyebabnya. Piramida memberi wejangan praktis: mulai dari unit test untuk logika inti, tambahkan integration test pada titik temu antar komponen, lalu sisakan sedikit end-to-end test untuk alur paling penting.

Anatomi Test: Arrange, Act, Assert #

Hampir semua test mengikuti pola tiga langkah yang dikenal sebagai Arrange, Act, Assert (AAA). Pertama, arrange: menyiapkan data dan kondisi awal — misalnya membuat nilai masukan. Kedua, act: menjalankan kode yang diuji, biasanya satu pemanggilan function. Ketiga, assert: membandingkan hasil nyata dengan hasil yang kita harapkan. Pola ini membuat test mudah dibaca karena pembaca selalu tahu di mana persiapan berakhir dan pemeriksaan dimulai.

Saatnya kita lihat pola tersebut dalam kode sungguhan. Simpan dua berkas berikut dalam satu folder, lalu jalankan perintah node --test diskon.test.mjs:

js
export function hitungDiskon(harga, persen) {
  return harga - (harga * persen) / 100;
}
js
import { test } from 'node:test';
import assert from 'node:assert';
import { hitungDiskon } from './diskon.mjs';

test('menghitung diskon 20 persen dari harga 150000', () => {
  const hasil = hitungDiskon(150000, 20);
  assert.strictEqual(hasil, 120000);
});

test('diskon 0 persen mengembalikan harga asli', () => {
  assert.strictEqual(hitungDiskon(80000, 0), 80000);
});
bash
node --test diskon.test.mjs

Output yang Dihasilkan:

text
✔ menghitung diskon 20 persen dari harga 150000
✔ diskon 0 persen mengembalikan harga asli
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

Mari kita telaah anatomi kedua test di atas:

  1. Baris 1 (import { test } from 'node:test'): function test adalah pintu masuk test runner bawaan Node.js; satu panggilan test() mendaftarkan satu kasus uji.
  2. Baris 2 (import assert ...): module node:assert menyediakan function pembanding; strictEqual pada contoh ini memaksa perbandingan ketat dengan ===.
  3. Baris 3: berkas diskon.mjs diimpor seperti module biasa — kode yang diuji tidak perlu tahu bahwa kode tersebut sedang diuji.
  4. Baris 5-8 (test pertama): argumen pertama adalah nama test yang menjelaskan perilaku yang dijamin; baris 6 adalah tahap act, dan baris 7 adalah tahap assert yang membandingkan hasil nyata 120000 dengan harapan.
  5. Baris 10-12 (test kedua): menguji kasus batas — persen 0 — untuk memastikan tidak ada syarat tersembunyi yang merusak kasus sederhana.

Prinsip FIRST #

Suite test yang sehat memenuhi lima kriteria yang dijuluki FIRST. Prinsip ini menjadi alat ukur cepat ketika kita ragu apakah sebuah test layak ditulis atau ditata ulang.

PrinsipArtinya
FastTest berjalan cepat sehingga kita berani menjalankannya sesering mungkin
IndependentSetiap test berdiri sendiri; urutan eksekusi tidak mengubah hasil
RepeatableHasil sama di komputer mana pun dan kapan pun, tanpa bergantung kondisi jaringan atau waktu
Self-validatingTest melapor lulus atau gagal secara otomatis, bukan lewat pemeriksaan manual
TimelyTest ditulis bersamaan dengan kodenya, bukan ditunda setelah seluruh fitur selesai

Dua prinsip pertama paling sering dilanggar tanpa sengaja. Test yang menunggu timer sungguhan selama lima detik melanggar fast; test yang mengandalkan data yang ditulis test sebelumnya melanggar independent. Sepanjang Section ini kita akan berulang kali melihat teknik menjaga kedua prinsip tersebut — mulai dari fake timer sampai server uji yang dibuat ulang di setiap test.

Ke Mana Setelah Ini #

Kita telah mengenal alasan menulis test, peta jenis pengujian, pola AAA, dan prinsip FIRST, serta menjalankan test pertama dengan node --test. Semua itu cukup sebagai fondasi konsep; tiba saatnya mengenal perangkatnya secara menyeluruh — test runner bawaan node:test yang gratis tanpa satu pun dependency, topik Chapter Test Runner Bawaan node:test.