Test Coverage & Strategi — Testing

Apa itu Test Coverage? Test coverage (code coverage) mengukur persentase kode yang dieksekusi saat menjalankan test suite. Ini memberikan gambaran seberapa…

Test coverage mengukur berapa persen kode kamu yang benar-benar dijalankan waktu test suite berjalan. Bayangin satpam yang patroli malam dan menandatangani kartu absen di tiap ruangan. Kartu yang penuh tanda tangan membuktikan dia lewat semua ruangan, tapi gak membuktikan dia mengecek jendelanya sudah dikunci. Coverage itu kartu absennya: dia tahu baris mana yang dilewati test, tapi gak tahu apa yang dicek di sana.

🧪 Tebakan cepat: satu test, hitungOngkir(150000, true), menjalankan semua baris function-nya, jadi line coverage-nya 100%. Branch coverage-nya berapa: 100%, 50%, atau 0%? Tebak dulu, jawabannya ada di bagian "Line coverage vs branch coverage".

Function yang mau diukur

Aturan ongkir sebuah toko: ongkirnya Rp20.000, member bayar setengahnya, dan belanja mulai Rp100.000 gratis ongkir.

// src/ongkir.js
export function hitungOngkir(total, member) {
  let ongkir = 20000;      // baris 2
  if (member) {            // baris 3
    ongkir = ongkir / 2;   // baris 4: member bayar setengah
  }
  if (total >= 100000) {   // baris 6
    ongkir = 0;            // baris 7: mulai 100 ribu gratis
  }
  return ongkir;           // baris 9
}
// src/ongkir.test.js
import { test, expect } from "vitest";
import { hitungOngkir } from "./ongkir.js";

test("member, belanja 150 ribu: gratis", () => {
  expect(hitungOngkir(150000, true)).toBe(0);
});

Menjalankan coverage

npm install -D @vitest/coverage-v8
npx vitest --coverage

Hasilnya untuk satu test tadi:

 Test Files  1 passed (1)
      Tests  1 passed (1)

 % Coverage report from v8
-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |       50 |     100 |     100 |                   
 ongkir.js |     100 |       50 |     100 |     100 | 3-6               
-----------|---------|----------|---------|---------|-------------------

Empat angka di laporannya

Line coverage vs branch coverage

Test yang dijalankan % Lines % Branch Uncovered Line #s
(150000, true) 100 50 3-6
(50000, false) 66,66 50 4,7
(150000, true) dan (50000, false) 100 100
(50000, true) dan (100000, false) 100 100

Makanya branch coverage lebih jujur daripada line coverage: line bisa 100% padahal separuh jalurnya belum pernah dicoba.

100% coverage bukan berarti bebas bug

Misalkan baris 6 salah ketik jadi if (total > 100000). Dua test tadi tetap hijau, dan coverage-nya tetap 100% untuk keempat angka. Padahal belanja tepat Rp100.000 harusnya gratis, dan function ini mengembalikan 20000. Bug-nya baru ketahuan kalau ada test tepat di batas:

test("tepat 100 ribu: gratis", () => {
  expect(hitungOngkir(100000, false)).toBe(0);
});
 FAIL  src/ongkir.test.js > tepat 100 ribu: gratis
AssertionError: expected 20000 to be +0 // Object.is equality

Lebih parah lagi, test tanpa expect sama sekali juga dihitung. test("jalan", () => { hitungOngkir(150000, true); }) memberi line coverage 100% dan gak mengecek apa pun. Coverage cuma mencatat baris yang jalan. Dia gak tahu hasilnya dicek atau gak, dan gak tahu edge case seperti nilai batas, nol, atau data kosong sudah dicoba atau belum. Cara mengecek apakah test-mu benar-benar menangkap kesalahan ada di pelajaran berikutnya, mutation testing.

Threshold dan target yang masuk akal

// vitest.config.js
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    coverage: {
      provider: "v8",
      reporter: ["text", "html"],
      include: ["src/**/*.js"],
      thresholds: {
        lines: 80,
        branches: 80,
        functions: 80,
        statements: 80,
      },
    },
  },
});

Target yang umum: sekitar 80% untuk kode bisnis yang penting (harga, pembayaran, validasi), dengan perhatian lebih ke branch. Boilerplate, file konfigurasi, dan type definition boleh dikecualikan. Mengejar 100% biasanya berakhir dengan test asal jalan yang gak mengecek apa-apa.

⚠️ Jebakan yang sering ketemu

TL;DR: Test coverage mengukur kode yang dijalankan test: % Stmts, % Branch (cabang if/else, ternary, &&), % Funcs, dan % Lines. Jalankan dengan npx vitest --coverage (atau npx vitest run --coverage di CI) setelah memasang @vitest/coverage-v8. Line coverage 100% bisa berarti branch cuma 50%, jadi perhatikan branch. 100% pun gak menjamin bebas bug: test bisa melewati semua baris tanpa mencoba edge case, bahkan tanpa expect. Pasang thresholds supaya CI gagal kalau coverage turun, targetkan sekitar 80% di kode penting, dan pakai laporannya untuk menemukan bagian yang belum dites.

Yang akan kamu pelajari