Suite test yang hijau terasa meyakinkan — sampai kita bertanya: bagian kode mana yang sebenarnya pernah dijalankan oleh test? Test yang hanya menyentuh jalur sukses akan tetap hijau meskipun seluruh cabang penanganan error tidak pernah diperiksa. Test coverage adalah pengukuran untuk kekosongan itu: metrik yang menunjukkan persentase baris, cabang, dan function yang benar-benar tereksekusi saat suite berjalan. Pada Chapter ini kita menjalankan laporan coverage bawaan Node.js, membaca kolom-kolomnya, menaikkan cakupan dengan test tambahan, dan memasang ambang batas agar angka tersebut dijaga otomatis oleh pipeline CI.

Menjalankan Laporan Coverage #

Node.js membawa penghitung coverage tanpa package tambahan: flag --experimental-test-coverage pada node --test. Sebagai bahan ukur, kita memakai module kecil dengan dua function — diskon yang memiliki cabang validasi, dan pajak yang lurus — beserta test yang sengaja hanya menguji satu jalur:

js
export function diskon(harga, persen) {
  if (persen < 0 || persen > 100) {
    throw new Error('persen harus di antara 0 dan 100');
  }
  return Math.round(harga * (1 - persen / 100));
}

export function pajak(harga, tarif) {
  return Math.round(harga * (tarif / 100));
}
js
import { test } from 'node:test';
import assert from 'node:assert';
import { diskon } from './perhitungan.mjs';

test('menghitung diskon yang sah', () => {
  assert.strictEqual(diskon(200000, 25), 150000);
});
bash
node --test --experimental-test-coverage perhitungan.test.mjs

Output yang Dihasilkan:

text
✔ menghitung diskon yang sah
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ ----------------------------------------------------------------
ℹ file            | line % | branch % | funcs % | uncovered lines
ℹ ----------------------------------------------------------------
ℹ perhitungan.mjs |  60.00 |    66.67 |   50.00 | 3-4 9-10
ℹ ----------------------------------------------------------------
ℹ all files       |  60.00 |    66.67 |   50.00 |
ℹ ----------------------------------------------------------------

Setelah rangkuman test yang sudah kita kenal, muncul blok laporan coverage berbentuk tabel. Angka-angkanya bersumber dari V8 — mesin JavaScript yang menandai baris mana yang tereksekusi selama proses berjalan — sehingga tidak ada instrumentasi manual yang perlu kita pasang. Pada terminal pembaca, tabel tersebut dibungkus baris pembuka dan penutup laporan; sesuai konvensi Section ini, output pada buku hanya menampilkan isi tabelnya.

Membaca Kolom Laporan #

KolomYang diukurPada laporan di atas
line %Persentase baris yang tereksekusi setidaknya sekali60.00 — dari 10 baris kode, 6 baris pernah berjalan
branch %Persentase cabang keputusan (if, `
funcs %Persentase function yang pernah dipanggil50.00 — hanya diskon; pajak belum pernah
uncovered linesDaftar baris yang tidak pernah tereksekusi3-4 9-10

Kolom uncovered lines adalah pintu masuk paling praktis: baris 3-4 adalah jalur penolakan throw di dalam diskon, sedangkan baris 9-10 adalah seluruh isi pajak. Dua baris pertama menunjukkan penyakit yang sering luput — test kita hanya memperlihatkan hari baik. Cabang penanganan error justru merupakan tempat kesalahan paling sering bersarang, karena jarang dijalankan manual saat mencoba aplikasi.

Menaikkan Cakupan #

Membaca laporan di atas, pekerjaan kita jelas: tambahkan test untuk persen negatif, persen di atas seratus, dan function pajak. Berkas kedua melakukan persis itu:

js
import { test } from 'node:test';
import assert from 'node:assert';
import { diskon, pajak } from './perhitungan.mjs';

test('menghitung diskon yang sah', () => {
  assert.strictEqual(diskon(200000, 25), 150000);
});

test('diskon negatif ditolak', () => {
  assert.throws(() => diskon(200000, -10), {
    message: 'persen harus di antara 0 dan 100',
  });
});

test('diskon di atas seratus ditolak', () => {
  assert.throws(() => diskon(200000, 150), {
    message: 'persen harus di antara 0 dan 100',
  });
});

test('menghitung pajak dari harga', () => {
  assert.strictEqual(pajak(150000, 11), 16500);
});
bash
node --test --experimental-test-coverage perhitungan-lengkap.test.mjs

Output yang Dihasilkan:

text
✔ menghitung diskon yang sah
✔ diskon negatif ditolak
✔ diskon di atas seratus ditolak
✔ menghitung pajak dari harga
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ ----------------------------------------------------------------
ℹ file            | line % | branch % | funcs % | uncovered lines
ℹ ----------------------------------------------------------------
ℹ perhitungan.mjs | 100.00 |   100.00 |  100.00 |
ℹ ----------------------------------------------------------------
ℹ all files       | 100.00 |   100.00 |  100.00 |
ℹ ----------------------------------------------------------------

Seluruh kolom kini 100.00 dan daftar uncovered lines kosong. Perhatikan bahwa dua test penolakan sekaligus menutup kolom branch %: kondisi persen < 0 dibuktikan lewat -10, kondisi persen > 100 lewat 150 — masing-masing sisi operator || mendapat gilirannya. Proses yang barusan kita lakukan — jalankan coverage, baca baris kosong, tulis test untuk menutupnya, ulangi — adalah daur kerja standar saat menumbuhkan suite test pada kode warisan yang belum teruji.

Ambang Batas untuk Pipeline CI #

Angka coverage hanya berguna jika dijaga. Flag --test-coverage-lines=80 menuntut paling sedikit 80 persen cakupan baris; jika tidak tercapai, runner keluar dengan kode gagal. Mari kita padukan flag tersebut dengan berkas test parsial yang masih 60 persen:

bash
node --test --experimental-test-coverage --test-coverage-lines=80 perhitungan.test.mjs
echo "kode keluar: $?"

Output yang Dihasilkan:

text
✔ menghitung diskon yang sah
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ Error: 60.00% line coverage does not meet threshold of 80%.
ℹ ----------------------------------------------------------------
ℹ file            | line % | branch % | funcs % | uncovered lines
ℹ ----------------------------------------------------------------
ℹ perhitungan.mjs |  60.00 |    66.67 |   50.00 | 3-4 9-10
ℹ ----------------------------------------------------------------
ℹ all files       |  60.00 |    66.67 |   50.00 |
ℹ ----------------------------------------------------------------
kode keluar: 1

Laporan tetap dicetak lengkap, diawali baris Error: 60.00% line coverage does not meet threshold of 80%., dan proses keluar dengan kode 1 — pipeline CI yang memeriksa kode keluar akan menghentikan build. Selain --test-coverage-lines, tersedia pasangannya untuk cabang (--test-coverage-branches) dan function (--test-coverage-functions), semuanya didokumentasikan pada halaman CLI Node.js . Ambang yang sehat biasanya tidak dari seratus persen: nilai 80-90 persen memberi ruang bagi kode penanganan khusus yang memang sulit diuji, sambil tetap menangkap kekosongan yang tidak disengaja.

Coverage Bukan Jaminan Mutu #

Angka seratus persen mudah dicapai dengan cara yang salah. Test berikut menaikkan cakupan pajak ke 100 persen tanpa memeriksa apa pun:

js Contoh test tanpa assertion yang bermakna
import { test } from 'node:test';
import { pajak } from './perhitungan.mjs';

test('memanggil pajak demi angka coverage', () => {
  pajak(150000, 11); // hasil dibuang, tidak ada assert
});

Blok di atas hanya ilustrasi — jangan ditiru. Function dipanggil sehingga seluruh barisnya terhitung tereksekusi, tetapi jika pajak keliru menghitung, tidak ada satu pun baris test yang protes. Coverage mengukur kode pernah dijalankan, bukan kode pernah diperiksa; kualitas assertion tidak masuk ke dalam metrik.

Ke Mana Setelah Ini #

Kita kini bisa mengukur seberapa jauh test menyentuh kode, menutup celah berdasarkan daftar uncovered lines, dan menjaga angkanya lewat ambang batas. Seluruh peralatan yang telah kita bangun — assertion, async test, mock, coverage — siap diangkat ke ukuran berikutnya: memastikan seluruh komponen bekerja benar saat disatukan. Chapter Integration Test untuk HTTP Server menyusun server sungguhan di dalam test dan mengujinya lewat fetch seperti halnya client.