Seluruh perangkat section ini sudah kita kuasai: node:test untuk menjalankan uji, keluarga assert untuk mengklaim, t.mock.method untuk menggantikan dependensi, jsdom untuk DOM, uji integration untuk sambungan antar-unit, dan Playwright untuk browser sungguhan. Perangkat yang lengkap tanpa strategi justru membawa bahaya baru: uji yang terlalu banyak, terlalu lambat, dan menguji hal yang salah.
Chapter penutup ini menyusun semua alat tadi menjadi keputusan harian yang bisa dipegang saat menulis kode: memilih apa yang layak diuji, memberi nama uji yang menjelaskan dirinya, menata isi uji dengan pola tetap, dan menentukan kapan lapisan yang lebih berat benar-benar dibutuhkan.
Memilih Apa yang Layak Diuji #
Aturan paling ringkas untuk menyeleksi kandidat uji: uji kode yang berisi keputusan, lewati kode yang hanya meneruskan. Fungsi hitungTotal menyimpan keputusan (urutan diskon dan pajak, pembulatan) — salah sedikit, uang pembeli berubah. Sebaliknya, getter yang hanya mengembalikan properti atau baris yang memipakan nilai dari satu fungsi ke fungsi berikutnya tidak membawa keputusan apa pun; mengujinya hanya menyalin isi implementasi.
| Layak diuji | Tidak perlu diuji langsung |
|---|---|
| Rumus bisnis: diskon, pajak, subtotal | Getter/setter satu baris tanpa validasi |
| Validasi dan pesan galat | Pemipaan nilai antar fungsi murni |
| Parser dan pemformat data (JSON, tanggal, teks) | console.log dan kode tampilan murni |
| State dan aturan transisi (keranjang, status pesanan) | Konfigurasi dan konstanta statis |
| Sambungan antar-unit (urutan, data bersama) | Wrapper tipis di atas API bawaan |
Panduan terakhir tabelnya: kode yang sudah diuji oleh pihak lain (misalnya method bawaan Array) tidak perlu kita uji ulang; yang kita uji adalah cara kode kita memakainya. Getter tanpa logika biasanya tercakup secara tidak langsung — ia ikut tereksekusi setiap kali uji lain membaca data melaluinya, dan coverage dari chapter sebelumnya memperlihatkan itu tanpa kita menulis uji khusus.
Nama Uji yang Deskriptif #
Nama uji adalah dokumentasi yang paling sering dibaca — ia muncul di laporan runner, di daftar kegagalan CI, dan di hasil git blame saat seseorang mencoba memahami aturan bisnis. Bandingkan dua gaya untuk fungsi yang sama:
// Nama yang tidak memberi informasi:
test('uji 1', () => {});
test('hitungTotal berjalan', () => {});
test('harus benar', () => {});
// Nama yang menceritakan kasus dan harapannya:
test('hitungTotal menghitung pajak dari harga setelah diskon', () => {});
test('hitungTotal tanpa kode kupon tidak memotong apa pun', () => {});
test('terapkanKupon dengan kode asing tidak memotong apa pun', () => {});Nama bagian atas tidak gagal dengan jelas: ketika hitungTotal berjalan berubah merah, kita belum tahu aturan mana yang dilanggar. Nama bagian bawah menyebut unit, kasus, dan hasil yang diharapkan dalam satu kalimat — pembaca laporan kegagalan bisa menebak akar masalahnya sebelum membuka berkas. Kebiasaan praktisnya: tulis nama uji seolah-olah kalimat itu akan dibaca bosan di barisan kegagalan pukul dua pagi.
Arrange-Act-Assert #
Isi setiap uji juga punya pola baku yang membuatnya terbaca seragam: Arrange-Act-Assert (AAA) — siapkan data, jalankan satu langkah yang diuji, lalu klaim hasilnya:
import { test } from 'node:test';
import assert from 'node:assert/strict';
test('pajak dihitung dari harga setelah diskon', () => {
// Arrange — siapkan data yang dibutuhkan.
const harga = 200000;
const persenDiskon = 25;
// Act — jalankan satu langkah yang sedang diuji.
const setelahDiskon = harga - harga * (persenDiskon / 100);
const pajak = Math.round(setelahDiskon * 0.11);
// Assert — pastikan hasil akhirnya benar.
assert.strictEqual(pajak, 16500);
});Output yang Dihasilkan:
✔ pajak dihitung dari harga setelah diskon (0.625965ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54.498292Pola ini memberi tiga manfaat praktis. Struktur yang seragam membuat mata bergerak cepat: bagian Arrange selalu data, bagian Act selalu satu pemanggilan. Uji yang kehilangan bagian Act — memanggil dua fungsi sekaligus, misalnya — adalah tanda uji integration yang menyamar sebagai unit, dan sebaiknya dipecah. Uji yang membutuhkan Arrange sepanjang dua puluh baris biasanya berarti unit yang diuji memiliki terlalu banyak dependensi, sehingga sinyalnya tertuju ke desain kode, bukan ke ujinya.
Memilih Lapisan untuk Setiap Gejala #
Gejala yang ditemukan seharusnya menentukan lapisan tempat uji pencegahnya ditulis:
- Angka salah di satu fungsi → uji unit. Uji unit paling cepat dijalankan dan langsung menunjuk pelakunya.
- Unit benar tetapi gabungannya salah → uji integration. Contoh yang sudah kita tangani: pajak yang dihitung dari harga sebelum diskon.
- Struktur DOM tidak sesuai → uji DOM dengan jsdom: fungsi render menghasilkan elemen dan teks yang bisa diklaim tanpa browser.
- Interaksi atau tampilan di browser sungguhan bermasalah → uji E2E dengan Playwright, untuk jalur utama saja.
Urutan piramide di section pertama kini punya makna operasional: dorong sebanyak mungkin pemeriksaan ke lapisan unit, tambahkan integration untuk sambungan, dan sisakan E2E untuk beberapa jalur yang kegagalannya paling mahal. Perubahan kecil seharusnya bisa diverifikasi dalam hitungan detik oleh uji unit — bila setiap perbaikan satu baris menuntut menjalankan seluruh rangkaian E2E, piramidanya terbalik dan biayanya akan terasa setiap hari.
Menjaga Rangkaian Tetap Sehat #
Rangkaian uji hanya berguna bila dipercaya, dan kepercayaan itu dirawat dengan tiga sifat:
- Cepat. Rangkaian unit sebaiknya selesai dalam hitungan detik agar dijalankan setiap kali menyimpan berkas, bukan sekali sepekan.
- Deterministik. Hasilnya sama setiap kali dijalankan: tanpa ketergantungan waktu nyata, nomor acak, urutan objek, atau jaringan. Uji yang kadang gagal (flaky) merusak kepercayaan lebih cepat daripada tidak ada uji — orang mulai mengabaikan tanda merah.
- Independen. Setiap uji menyiapkan datanya sendiri dan tidak mengandalkan urutan eksekusi; uji ke-7 harus tetap lulus saat dijalankan sendirian.
Kebiasaan penutup yang paling bernilai: setiap bug yang ditemukan menjadi satu uji baru sebelum diperbaiki. Uji tersebut mereproduksi bug, gagal dulu, lalu hijau setelah perbaikan — dan berjaga seumur hidup proyek. Dengan disiplin itu, rangkaian uji tumbuh persis di titik-titik yang pernah menyakitkan, dan chapter selanjutnya di section berikutnya (tooling dan code quality) mengambil alih tugas menjaga kualitas dari sisi alat: linter, formatter, dan otomasi build.