Uji modul ongkir toko Budi lulus semua — empat tanda ✔ hijau di terminal. Tetapi lulus semua bukan berarti semuanya sudah diperiksa: fungsi cekBerat yang baru ditambahkan belum pernah dipanggil satu pun oleh uji, dan tidak ada yang memberi tahu kita. Pertanyaan “seberapa banyak kode yang benar-benar dijalankan oleh uji?” dijawab oleh test coverage (cakupan pengujian).

Coverage diukur dengan cara instrumen: saat uji berjalan, Node mencatat baris, cabang, dan fungsi mana yang benar-benar dieksekusi, lalu menyusun persentasenya. Chapter ini memakai fitur bawaan test runner — satu flag tambahan, tanpa alat pihak ketiga — untuk menemukan celah pengujian, menambalnya, dan belajar menafsirkan angkanya dengan kepala dingin.

Mengukur dengan Flag Bawaan #

Berkas modulnya sengaja memuat bagian yang belum teruji — fungsi cekBerat belum disentuh uji mana pun, dan cabang tarif ekspres juga belum:

// kalkulator-ongkir.mjs — menghitung biaya pengiriman pesanan.

export function hitungOngkir(subtotal, berat, ekspres = false) {
  if (subtotal >= 250000) return 0;
  const dasar = ekspres ? 25000 : 15000;
  const tambahan = Math.max(0, berat - 2) * 3000;
  return dasar + tambahan;
}

export function cekBerat(berat) {
  if (berat <= 0) {
    throw new RangeError('berat harus lebih dari nol');
  }
  return berat;
}

Berkas ujinya baru mencakup tiga kasus hitungOngkir:

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { hitungOngkir } from './kalkulator-ongkir.mjs';

test('subtotal di atas ambang mendapat gratis ongkir', () => {
  assert.strictEqual(hitungOngkir(300000, 5), 0);
});

test('ongkir dasar berlaku sampai dua kilogram', () => {
  assert.strictEqual(hitungOngkir(100000, 2), 15000);
});

test('setiap kilogram tambahan dikenakan biaya', () => {
  assert.strictEqual(hitungOngkir(100000, 4), 21000);
});

Sekarang tambahkan flag --experimental-test-coverage pada perintah biasa:

bash
node --test --experimental-test-coverage uji-ongkir.test.mjs

Output yang Dihasilkan:

text
✔ subtotal di atas ambang mendapat gratis ongkir (0.845711ms)
✔ ongkir dasar berlaku sampai dua kilogram (0.918795ms)
✔ setiap kilogram tambahan dikenakan biaya (0.13973ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 66.242096
ℹ start of coverage report
ℹ ----------------------------------------------------------------------
ℹ file                  | line % | branch % | funcs % | uncovered lines
ℹ ----------------------------------------------------------------------
ℹ kalkulator-ongkir.mjs |  66.67 |    83.33 |   50.00 | 11-15
ℹ ----------------------------------------------------------------------
ℹ all files             |  66.67 |    83.33 |   50.00 | 
ℹ ----------------------------------------------------------------------
ℹ end of coverage report

Semua uji tetap lulus, dan setelah ringkasan biasa runner menambahkan tabel coverage. Kolom file menunjuk modul yang diukur; tiga kolom persentase diikuti kolom uncovered lines yang memuat daftar baris yang tidak pernah dieksekusi.

Membaca Tabel Coverage #

Empat kolom angka tabel di atas memberi cerita berbeda:

  • line % = 66.67 — dua pertiga baris dieksekusi. Baris 11–15 (seluruh badan cekBerat) tidak pernah dijalankan; itulah yang dilaporkan kolom uncovered lines.
  • branch % = 83.33 — cabang percabangan hampir penuh, tetapi tidak 100: ternary ekspres ? 25000 : 15000 hanya pernah mengambil sisi false, dan cabang throw di cekBerat juga tak pernah terjadi. Coverage branch menuntut setiap sisi keputusan dicoba, bukan hanya jalur yang lazim.
  • funcs % = 50.00 — dari dua fungsi yang diekspor, hanya hitungOngkir yang pernah dipanggil.

Tabel ini persis alasan chapter dibuka dengan cerita uji yang “lulus semua”: runner tidak pernah mengeluh soal cekBerat, karena tidak ada asersi yang menyentuhnya. Coverage yang membuat kebocoran itu terlihat.

Menambal Celah yang Ditemukan #

Perbaikannya mengikuti pola yang sudah kita kenal: dua uji baru, satu untuk cabang ekspres dan satu untuk cekBerat. Pertama, perbarui baris import di bagian atas uji-ongkir.test.mjs agar cekBerat ikut diambil:

js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { hitungOngkir, cekBerat } from './kalkulator-ongkir.mjs';

Kedua, tambahkan dua uji berikut di bagian akhir berkas yang sama:

js
test('ongkir ekspres memakai tarif dasar berbeda', () => {
  assert.strictEqual(hitungOngkir(100000, 1, true), 25000);
});

test('berat nol atau negatif ditolak', () => {
  assert.throws(() => cekBerat(0), RangeError);
  assert.strictEqual(cekBerat(3), 3);
});

Jalankan ulang perintah yang sama, dan tabelnya berubah menjadi:

Output yang Dihasilkan:

text
ℹ start of coverage report
ℹ ----------------------------------------------------------------------
ℹ file                  | line % | branch % | funcs % | uncovered lines
ℹ ----------------------------------------------------------------------
ℹ kalkulator-ongkir.mjs | 100.00 |   100.00 |  100.00 | 
ℹ ----------------------------------------------------------------------
ℹ all files             | 100.00 |   100.00 |  100.00 | 
ℹ ----------------------------------------------------------------------
ℹ end of coverage report

Lima uji kini menjangkau seluruh baris, seluruh cabang (ekspres true dan false, throw dan kembalinya), dan kedua fungsi. Nilai uncovered lines kosong karena tidak ada lagi baris yang terlewat.

Menafsirkan Angka dengan Sehat #

Coverage adalah alat pencari lubang, bukan skor prestasi. Tiga prinsip penafsiran yang perlu dibawa:

  • 100 persen bukan jaminan bebas bug. Coverage hanya menjamin baris dieksekusi — bukan bahwa asersinya tepat. Uji hitungOngkir(300000, 5), 0 yang diklaim 1 akan gagal, tetapi uji tanpa asersi sama sekali akan tetap hijau sambil mengangkat angka coverage. Persentase tinggi dengan asersi lemah adalah kesan, bukan perlindungan.
  • Angka yang rendah justru berguna. 66 persen memberi tahu persis ke mana kita harus menulis uji berikutnya — di sinilah nilai alat ini, dan tidak perlu mengejar angka sempurna pada kode yang memang tidak bernilai uji (misalnya pemanggilan console.log di beberapa baris sisa).
  • Kebijakan proyek lebih penting daripada angka absolut. Banyak tim memasang ambang minimal (misalnya tidak boleh turun) agar fitur baru wajib datang dengan ujinya, daripada mengejar 100 persen di seluruh basis kode lama.

Sampai di sini seluruh perangkat teknis sudah lengkap: runner, asersi, test double, DOM tiruan, uji integration, otomasi browser, dan pengukuran cakupan. Chapter terakhir menyatukan semuanya menjadi keputusan harian: apa yang layak diuji, bagaimana menamai ujinya, dan kapan menambah lapisan yang lebih berat.