Toko daring Budi baru saja menggelar promo 17 Agustus: diskon 50 persen untuk produk terpilih. Kode halaman checkout diubah sedikit, promo berjalan lancar, dan semua pihak puas. Dua hari kemudian pesanan mulai aneh: keranjang tanpa promo juga terpangkas harganya. Perubahan kecil untuk fitur baru ternyata merusak fitur lama yang sebelumnya benar.
Kondisi semacam ini punya nama: regression (regresi) — perilaku yang dulu sudah benar menjadi salah setelah kode diubah. Chapter pembuka section ini membahas kenapa regresi tidak bisa diandalkan pada pemeriksaan manual, bagaimana uji otomatis menguncinya, dan bagaimana pengujian dibagi menjadi tiga lapisan: unit, integration, dan end-to-end (E2E). Kita juga mengenalkan dua peran alat yang muncul terus-menerus di section ini: test runner dan assertion library.
Regresi dan Nilai Sebuah Uji Otomatis #
Pemeriksaan manual punya kelemahan mendasar: ia hanya mencakup kasus yang diingat pemeriksanya saat itu. Setelah berbulan-bulan dirilis, jumlah kombinasi fitur tumbuh jauh melebihi daya ingat manusia. Uji otomatis menyelesaikannya dengan prinsip sederhana: kode yang memeriksa kode. Kita menulis satu fungsi kecil yang menjalankan fungsi lain, membandingkan hasilnya dengan nilai yang diharapkan, dan melaporkan ketidaksesuaian secara otomatis.
Lihat bagaimana bug promo Budi tertangkap. Versi lama fungsi diskon memuat kesalahan — angka persen diperlakukan seperti rupiah:
import assert from 'node:assert/strict';
function hargaDiskonBuggy(harga, persenDiskon) {
return Math.round(harga - persenDiskon); // bug: angka 50 dipakai seperti Rp50
}
const hasil = hargaDiskonBuggy(75000, 50);
console.log('hasil buggy:', hasil);
try {
assert.strictEqual(hasil, 37500);
} catch (err) {
console.log(err.message);
}Output yang Dihasilkan:
hasil buggy: 74950
Expected values to be strictly equal:
74950 !== 37500Fungsi hargaDiskonBuggy mengurangi 75000 dengan 50 dan menghasilkan 74950 — persis gejala yang dikeluhkan pembeli. Pemanggilan assert.strictEqual(hasil, 37500) bertugas mengklaim bahwa kedua nilai harus identik; karena klaim itu tidak terpenuhi, asersi melempar AssertionError dengan pesan yang membandingkan kedua sisi. Pesan inilah yang nantinya dibaca test runner dan dilaporkan sebagai kegagalan uji.
Berikut versi yang sudah benar, dikunci dengan uji regresi agar bug yang sama tidak bisa masuk lagi. Berkas ini berupa uji sungguhan yang bisa dijalankan test runner Node:
import { test } from 'node:test';
import assert from 'node:assert/strict';
// Versi yang sudah diperbaiki. Bug lama: diskon dihitung sebagai
// pengurangan rupiah, sehingga angka 50 dipakai seperti Rp50.
function hargaDiskon(harga, persenDiskon) {
return Math.round(harga * (1 - persenDiskon / 100));
}
test('regresi promo 17 Agustus: diskon 50 persen untuk harga 75000', () => {
assert.strictEqual(hargaDiskon(75000, 50), 37500);
});
test('tanpa diskon, harga tidak berubah', () => {
assert.strictEqual(hargaDiskon(75000, 0), 75000);
});Output yang Dihasilkan:
✔ regresi promo 17 Agustus: diskon 50 persen untuk harga 75000 (0.707848ms)
✔ tanpa diskon, harga tidak berubah (0.128707ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 52.938728Mari kita telaah baris-baris pentingnya:
- Baris 1 (
import { test } from 'node:test'):node:testadalah modul bawaan Node.js yang berperan sebagai test runner. Setiap pemanggilantest()mendaftarkan satu uji bernama. - Baris 2 (
import assert from 'node:assert/strict'): modulnode:assertadalah assertion library; modestrictmemaksa pembandingan yang ketat sehingga1dan'1'dianggap berbeda. - Baris 6–8 (
function hargaDiskon(...)): unit yang diuji. Perhatikan pemaknaanharga * (1 - persenDiskon / 100): diskon 50 persen mengalikan harga dengan0.5, sehingga75000menjadi37500. - Baris 10–12 (uji pertama): nama uji ditulis sebagai kalimat lengkap yang menyebut konteks bug (
regresi promo 17 Agustus) beserta angka kasusnya — lima tahun kemudian pun isi uji masih bisa dipahami. - Baris 14–16 (uji kedua): kasus batas yang dulunya rusak oleh bug — tanpa promo, harga tidak boleh berubah. Uji regresi biasanya berisi satu kasus yang pernah salah, ditambah beberapa kasus batas di sekitarnya.
Keluaran di atas memperlihatkan simbol ✔ untuk setiap uji yang lulus, lalu ringkasan jumlah uji (tests 2), jumlah yang lulus (pass 2), dan yang gagal (fail 0). Angka durasi seperti 0.707848ms berbeda-beda setiap dijalankan karena mengukur waktu eksekusi mesin Anda.
Tiga Lapis Piramide Testing #
Uji otomatis tidak ditulis dalam satu bentuk besar, melainkan disusun berlapis. Susunan lazim di industri disebut test pyramid (piramide pengujian): dasarnya lebar berisi banyak uji unit, bagian tengah berisi uji integration yang lebih sedikit, dan puncaknya kecil berisi uji E2E yang paling mahal.
| Lapisan | Apa yang diuji | Contoh di section ini | Biaya & kecepatan |
|---|---|---|---|
| Unit | Satu unit terkecil: fungsi, method, atau class secara terpisah | hargaDiskon, totalKeranjang | Paling murah, paling cepat |
| Integration | Kerja sama beberapa unit dalam satu alur | Keranjang + kupon + pajak | Sedang |
| End-to-end (E2E) | Aplikasi utuh dijalankan seperti pengguna asli, biasanya lewat browser | Alur beli sampai halaman pembayaran | Paling mahal, paling lambat |
Mengapa E2E paling sedikit jumlahnya? Satu uji E2E menjalankan browser sungguhan, memuat halaman, dan mengeksekusi ratusan baris kode sekaligus. Jika ia gagal, penyebabnya bisa di mana saja — logika, jaringan, tampilan, atau data — sehingga mencari akar masalahnya butuh waktu. Uji unit yang gagal justru langsung menunjuk satu fungsi yang salah. Karena itu strateginya: uji logika secara tuntas di lapisan unit, gunakan integration untuk menyambung unit, dan sisakan E2E untuk beberapa jalur utama saja.
Test Runner dan Assertion Library #
Dua istilah alat yang perlu dibedakan sejak awal:
- Test runner — program yang menemukan berkas uji, menjalankannya, mencatat lulus atau gagal, lalu menyusun laporan dan kode keluar. Di Node.js perannya dipegang
node:test, yang dijalankan lewat perintahnode --test. - Assertion library — kumpulan fungsi pemeriksa seperti
assert.strictEqualdanassert.deepStrictEqualyang melempar galat ketika klaim tidak terpenuhi. Di Node.js perannya dipegang modulnode:assert.
Keduanya bekerja berpasangan: runner menjalankan fungsi uji, asersi di dalamnya yang menentukan lulus atau gagalnya. Bila sebuah asersi melempar galat, runner menangkapnya, menandai uji tersebut gagal, lalu melanjutkan ke uji berikutnya — satu kegagalan tidak menghentikan rangkaian. Node.js mengunggulkan setup ini karena keduanya sudah bawaan: tanpa instalasi apa pun, kita sudah bisa menulis uji pertama.
Framework pihak ketiga seperti Jest atau Vitest menggabungkan kedua peran itu dalam satu paket dengan fitur tambahan, sedangkan Playwright khusus mengambil lapisan E2E karena memerlukan browser sungguhan. Semua alat tersebut memakai pola yang sama — runner, asersi, dan pelaporan — hanya nama dan kenyamanannya yang berbeda. Setelah pola dasarnya dipahami di sini, berpindah alat hanya soal sintaks.
Dengan peta besar di tangan — regresi, piramide tiga lapis, dan pasangan runner–assertion — kita siap menulis uji unit pertama dengan test runner bawaan Node.js.