Lewati ke konten

Penjelasan mutation testing

Spec: ISO/IEC/IEEE 29119-4 Spec: PHPUnit Evidence: Test-backed

Cakupan baris memberi tahu Anda bahwa suatu baris dijalankan saat suite pengujian berjalan. Cakupan baris tidak memberi tahu apakah ada pengujian yang akan gagal jika baris itu salah. Mutation testing menutup celah itu dengan merusak kode secara sengaja dan memeriksa apakah pengujian menyadarinya. Halaman ini menjelaskan arti sebuah mutation score dan bagaimana NextPDF menggunakannya sebagai alat diagnostik, bukan sebagai piala.

Cakupan adalah salah satu metrik pengujian yang paling dipercaya, sekaligus salah satu yang paling menyesatkan. Pengujian yang memanggil sebuah metode tetapi tidak melakukan asersi apa pun tetap menjalankan setiap baris di dalamnya: cakupan sempurna, deteksi nol. Literatur standar menyatakan secara tegas bahwa urutan di antara kriteria cakupan tidak memberi indikasi apa pun tentang kemampuannya mengungkap kesalahan. Kemampuan itu adalah properti yang disebut sebagai efektivitas pengujian (ISO/IEC/IEEE 29119-4, §C.2.4). Persentase cakupan dan jaminan menemukan kesalahan adalah dua klaim yang berbeda.

Bagi sebuah mesin PDF, hal ini bukan sekadar teori. Pemeriksaan byte-range tanda tangan, offset cross-reference, cabang pengodean — pengujian bisa “mencakup” semua ini sepenuhnya tanpa pernah mengasersi nilai yang penting. Suite yang hijau di atas fondasi pengujian yang lemah lebih buruk daripada celah yang jujur, karena justru membuat orang enggan memeriksa lebih lanjut.

  • Mutation testing menghasilkan ribuan suntingan kecil yang disengaja (mutant) pada kode sumber — mengubah < menjadi <=, + menjadi -, atau nilai return — lalu menjalankan ulang pengujian terhadap masing-masing mutant.
  • Jika sebuah pengujian gagal pada suatu mutant, mutant itu terbunuh: ada pengujian yang benar-benar mengasersi perilaku tersebut. Jika setiap pengujian tetap lolos, mutant itu lolos: perilakunya dijalankan tetapi tidak pernah diperiksa.
  • Mutation Score Indicator (MSI) secara kasar adalah jumlah mutant yang terbunuh dibagi total mutant yang tidak ekuivalen. MSI mengukur apakah pengujian Anda mendeteksi perubahan, bukan apakah pengujian itu menjalankan kode.
  • Sebagian mutant bersifat ekuivalen — mutant tersebut tidak dapat mengubah perilaku yang dapat diamati, sehingga tidak ada pengujian yang dapat membunuhnya. Menghitung mutant semacam ini sebagai kegagalan tidaklah jujur. NextPDF membuktikan dan mencatatnya dalam ledger alih-alih mengabaikannya secara informal.
  • NextPDF menggunakan MSI untuk menemukan dan memperkuat pengujian yang lemah. Ini adalah gerbang diagnostik dalam continuous integration, bukan angka pemasaran.

Mutasi dijalankan pada mesin dengan mutator Infection. Mutator ini dikonfigurasi untuk pohon kode sumber produksi, dengan keluarga mutator aritmetika, boolean, batas-kondisi, kesetaraan, nilai-balik, dan penghapusan diaktifkan — persis operator yang mengungkap logika yang “dijalankan tetapi tidak diasersi”. Alurnya mekanis:

  1. Start green The suite must pass before mutation begins.
  2. Mutate Apply one small, deliberate change to the source.
  3. Re-run Run the tests that cover the mutated line.
  4. Killed A test failed — the behaviour is genuinely asserted.
  5. Escaped All tests still pass — a weak spot to strengthen.
  6. Equivalent No test can kill it because behaviour is unchanged — proven and ledgered, not scored as a miss.
Lingkaran mutation testing yang dijalankan NextPDF: ambil pengujian yang hijau, hasilkan sebuah mutant, jalankan ulang pengujian yang mencakupnya, lalu klasifikasikan mutant tersebut sebagai terbunuh (sebuah pengujian menangkapnya), lolos (celah tercakup-tetapi-tanpa-asersi yang harus diperbaiki), atau terbukti-ekuivalen (tidak ada pengujian yang dapat membunuhnya; dicatat dalam ledger, tidak diperhitungkan terhadap skor).

Dua keputusan desain membuat angka ini layak dipercaya. Pertama, skor ini dijadikan sebuah gerbang. Continuous integration menegakkan MSI minimum (dan covered-MSI minimum) serta menjalankan varian bercakupan-diff pada baris yang berubah. Akibatnya, perubahan yang menambahkan kode tanpa asersi nyata akan tertangkap saat peninjauan, bukan baru ditemukan kemudian. Kedua, NextPDF tidak diam-diam mengabaikan mutant yang merepotkan. Mutant yang benar-benar ekuivalen secara semantik — misalnya !== versus != ketika strict typing menjamin kedua operan memiliki tipe yang sama — dicatat dalam sebuah mutation ledger dengan pengujian bukti ekuivalensi yang eksplisit. Akibatnya, jumlah mutant yang lolos mencerminkan celah yang nyata, bukan pembukuan semata. PHPStan Level 10 ditambah strict_types serta properti bertipe itulah yang membuat bukti ekuivalensi tersebut sahih.

Evidence: Test-backed Mutation testing dikonfigurasi pada mesin untuk direktori kode sumber produksi dengan keluarga mutator pengungkap perilaku diaktifkan. Konfigurasi ini ditegakkan sebagai gerbang continuous-integration dengan MSI minimum dan varian bercakupan-diff. Ini adalah pemeriksaan build, bukan pertimbangan belakangan.

Evidence: Test-backed Masalah mutant-ekuivalen ditangani secara jujur. Mutant yang ekuivalen secara semantik diklasifikasikan dan didukung oleh pengujian bukti-ekuivalensi khusus dalam sebuah mutation ledger, dengan kesahihan setiap bukti bertumpu pada PHPStan Level 10 ditambah strict typing. Karena itu, jumlah mutant yang lolos mewakili perilaku tak-terdeteksi yang nyata, bukan derau tak-terbunuh yang dilebih-lebihkan hingga membuat skor tampak lebih buruk.

Evidence: Standard-backed Mutasi adalah teknik yang diakui, bukan ciptaan NextPDF. Spec: ISO/IEC/IEEE 29119-4, §B.2.4 menjelaskan penerapan mutasi generik pada elemen-elemen sebuah spesifikasi untuk menurunkan mutasi spesifik untuk pengujian. Teknik ini diperlukan karena standar yang sama menyatakan bahwa relasi subsumes dari kriteria cakupan tidak mengurutkannya berdasarkan kemampuan mengungkap kesalahan (ISO/IEC/IEEE 29119-4, §C.2.4).

Evidence: Standard-backed Cakupan itu sendiri terdefinisi dengan baik dan terbatas. Spec: PHPUnit membedakan cakupan baris, cabang, dan jalur. Cakupan baris hanya mencatat bahwa sebuah baris yang dapat dieksekusi dijalankan. Memahami definisinya itulah yang membuat ketidakcukupannya menjadi jelas.

Yang penting bukanlah perintahnya — melainkan apa yang diberitahukan oleh sebuah mutant yang lolos:

<?php
declare(strict_types=1);
final class ByteRange
{
// Suppose the production guard is:
// if ($offset < 0) { throw new InvalidByteRange(); }
public function assertNonNegative(int $offset): void
{
if ($offset < 0) {
throw new InvalidByteRange('offset must be >= 0');
}
}
}
// A test that EXECUTES this line but does not assert the boundary:
// $byteRange->assertNonNegative(5); // no exception expected, none asserted
// gives 100% line coverage of assertNonNegative().
//
// Mutation flips `< 0` to `<= 0`. Behaviour now differs ONLY at $offset === 0.
// If no test passes 0 and asserts what happens, every test still passes:
// the mutant ESCAPED. Coverage said "tested"; mutation said "the boundary
// is unasserted". The fix is a test that pins offset === 0, not a higher
// target.
//
// composer mutation:diff → mutate only changed lines, enforce min MSI
// composer mutation:full → full-tree mutation gate

Di situlah letak nilai mutant yang lolos. Ia menemukan asersi yang nyata, spesifik, dan hilang yang oleh laporan cakupan dinilai telah diuji sepenuhnya.

Kesalahpahaman utamanya adalah bahwa mutation score merupakan nilai yang harus dimaksimalkan. MSI yang sangat tinggi yang dicapai dengan menulis pengujian untuk membunuh mutant sama hampanya dengan cakupan tinggi yang dicapai dengan memanggil metode tanpa melakukan asersi. Metrik itu telah dimanipulasi dan tidak lagi mengukur deteksi. NextPDF menggunakan MSI untuk menemukan pengujian yang lemah. Hasil yang diharapkan adalah asersi yang lebih baik; berbangga dengan angka jelas bukan tujuannya.

Kesalahpahaman kedua adalah bahwa setiap mutant yang bertahan merupakan cacat pada pengujian. Sebagian mutant benar-benar ekuivalen dan tidak dapat dibunuh oleh pengujian mana pun, karena tidak mengubah perilaku yang dapat diamati. Memperlakukan mutant semacam itu sebagai kegagalan menghasilkan skor yang tidak jujur dan rendah secara artifisial, serta mendorong orang untuk mengabaikan laporan tersebut. Jawaban NextPDF adalah membuktikan ekuivalensi secara eksplisit dan mencatatnya dalam ledger, bukan diam-diam menyembunyikannya atau berpura-pura angkanya lebih buruk daripada yang sebenarnya.

Mutation testing mengukur apakah pengujian mendeteksi perubahan yang disuntikkan. Ini tidak membuktikan bahwa kode itu benar. Ini tidak mengukur performa atau kesesuaian. Ini tidak dapat membunuh mutant yang benar-benar ekuivalen. Mutation score saat ini, ambang batas MSI-minimum yang berlaku, jumlah mutant ekuivalen yang tercatat dalam ledger, dan angka cakupan apa pun merupakan sinyal kualitas yang hidup, yang dihasilkan dari artefak continuous-integration dan dipublikasikan bersama build. Angka-angka itu sengaja tidak dicantumkan di sini, karena angka yang ditempelkan ke dalam prosa akan usang dan menjadi kebohongan kecil. Satu-satunya fakta stabil yang dinyatakan halaman ini adalah PHPStan Level 10, dan itu adalah properti konfigurasi yang menjadi landasan bukti ekuivalensi, bukan sebuah pengukuran.

Pemilihan mutator, ambang batas, dan kebijakan ledger dikelola oleh konfigurasi mutasi mesin dan dapat berkembang. Konfigurasi itulah yang menjadi otoritas jika suatu saat bertentangan dengan halaman ini. Tidak ada klaim apa pun yang dibuat di sini mengenai efektivitas pengujian pustaka lain mana pun.

  • Mutant — satu perubahan kecil yang sengaja dilakukan pada kode sumber, digunakan untuk menguji apakah suite pengujian akan menyadari perubahan tersebut.
  • Mutant terbunuh — mutant yang membuat setidaknya satu pengujian gagal; perilakunya benar-benar diasersi.
  • Mutant lolos — mutant yang membuat setiap pengujian tetap lolos. Perilakunya dijalankan tetapi tidak pernah diasersi — sebuah titik lemah yang harus diperbaiki.
  • Mutant ekuivalen — mutant yang tidak dapat mengubah perilaku yang dapat diamati, sehingga tidak ada pengujian yang dapat membunuhnya. NextPDF membuktikan dan mencatatnya dalam ledger.
  • MSI (Mutation Score Indicator) — secara kasar, mutant yang terbunuh dibagi total mutant yang tidak ekuivalen; sebuah ukuran deteksi, bukan eksekusi.
  • Cakupan baris — metrik yang hanya mencatat bahwa sebuah baris yang dapat dieksekusi dijalankan selama suite berjalan; didefinisikan oleh PHPUnit, dan tidak memadai jika berdiri sendiri.