100% Coverage Tidak Sama dengan Test yang Baik
Line coverage 100% cuma berarti setiap baris pernah dieksekusi test. Tidak berarti test itu benar. Contoh:
function isAdult(age) {
return age >= 18;
}
test("adult check", () => {
isAdult(20); // tidak ada assertion! tapi coverage 100%
});
Test di atas memberi 100% coverage, tapi apapun implementasi isAdult akan lulus — termasuk return age > 18 (bug off-by-one). Mutation testing dirancang untuk menangkap kelemahan seperti ini.
Cara Kerja Mutation Testing
- Framework memodifikasi kode aslimu dengan mutasi kecil (mutant)
- Jalankan test suite terhadap tiap mutant
- Jika test gagal (detect mutasi) → mutant killed (bagus)
- Jika test tetap lulus → mutant survived (test-mu lemah!)
Jenis Mutation Operators
| Kategori | Original | Mutant |
|---|---|---|
| Arithmetic | a + b | a - b, a * b |
| Conditional (relational) | a >= b | a > b, a <= b, a == b |
| Logical | a && b | a || b |
| Boolean literal | return true | return false |
| String | "hello" | "" |
| Increment | i++ | i-- |
| Block removal | body function | dihapus/return undefined |
Tools
- Stryker — untuk JavaScript/TypeScript.
npx stryker run - Infection — untuk PHP. Integrasi kuat dengan PHPUnit/Pest:
vendor/bin/infection - PIT/Pitest — untuk Java, Kotlin
- mutmut — untuk Python
Contoh Konfigurasi Stryker
// stryker.conf.json
{
"packageManager": "npm",
"testRunner": "vitest",
"mutate": ["src/**/*.ts", "!src/**/*.test.ts"],
"thresholds": { "high": 80, "low": 60, "break": 50 },
"reporters": ["html", "progress"]
}
Mutation Score
Metrik utamanya:
Mutation Score = (killed mutants / total mutants) × 100%
Contoh: dari 100 mutant, 85 killed, 15 survived → skor 85%. Skor ini lebih bermakna daripada line coverage karena menguji kualitas assertion, bukan sekadar eksekusi.
Target realistis: 75-85% untuk project produksi. 100% sering tidak praktis karena ada equivalent mutant — mutasi yang tidak mengubah behaviour (mis. i < arr.length vs i != arr.length).
Survived Mutation sebagai Signal
Mutation survived adalah gold untuk memperbaiki test suite. Contoh hasil report:
Survived mutant di src/pricing.ts:12
Original: if (total >= 100000) applyDiscount();
Mutant: if (total > 100000) applyDiscount();
→ Test-mu tidak menguji edge case total === 100000
→ Tambah: it("tepat di batas 100000, harus diskon", ...)
CI Gating
# Stryker: fail CI jika mutation score turun
npx stryker run --break 75
# Infection: --min-msi=80 artinya minimum 80%
vendor/bin/infection --min-msi=80 --min-covered-msi=85
Kapan & Kapan TIDAK Pakai
- Pakai: kode bisnis kritis (pricing, autentikasi, validasi) — di sini test harus kuat
- Jangan: UI sederhana, boilerplate, type definition — biaya komputasi tinggi untuk nilai rendah
- Jalankan mingguan, bukan tiap commit — mutation testing lambat (bisa 10-100× lebih lama dari unit test)
- Mulai dari file kritis dulu, perluas bertahap