Assertion LIKE yang Licik: Saat Nomor 1 Kena Nomor 19
Pola LIKE atas JSON ter-serialisasi membuat assertion integration test melenceng: nomor 1 tertukar nomor 19 karena collision substring.
Ringkasan
Test assertion pakai LIKE '%"no":1%' buat nyari JSON, eh taunya nyangkut ke objek nomor 19 gara-gara 16 dan 19 sama-sama diawali "no":1. Jadi flake-nya murni soal substring dan urutan data, bukan race condition. Solusinya bandingin nilai hasil parsing atau pakai JSON_SEARCH, jangan LIKE buat predikat identitas.
Integration test salah satu modul importer punya assertion yang kelihatan sehat: cari satu baris di tabel lalu bandingkan longitude-nya. Query-nya memakai pola seperti LIKE '%"no":1%' untuk menemukan objek dengan nomor 1 di kolom berisi JSON. Test itu lolos berminggu-minggu, lalu sekali jalan hasilnya melenceng: longitude yang keluar milik objek nomor 19, bukan nomor 1.
Jejak flake yang bukan race condition
Refleks pertama saat mendengar test flaky biasanya menyalahkan urutan data atau koneksi. Tapi kejadian ini mekanismenya murni string. Kolom yang dicari menyimpan JSON ter-serialisasi, dan pola yang dipakai adalah substring. Di JSON itu ada beberapa objek: "no":1, "no":16, dan "no":19. Pola pencarian "no":1 cocok dengan semua karena dua nomor lain sama-sama berawalan "no":1. Yang menentukan baris mana yang ketangkap cuma urutan; begitu susunan data bergeser, baris lain yang muncul.
Dokumentasi MySQL merinci dua wildcard LIKE: % yang "matches any number of characters, even zero characters" dan _ yang "matches exactly one character" [1]. Keduanya membuat LIKE berguna untuk pencarian longgar, dan persis itu yang membuatnya salah dipakai sebagai predikat identitas. Nomor 1, 16, dan 19 adalah nilai yang berbeda; pencarian yang benar harus memperlakukan mereka sebagai nilai penuh, bukan awalan.
Kapan LIKE sah, kapan dia penipu
Saya tetap pakai LIKE untuk keperluan yang memang longgar: filter admin berdasarkan potongan nama, laporan berisi "semua yang mengandung kata X". Di situ kecocokan sebagian justru diminta. Yang berbahaya adalah ketika substring dipakai untuk memilih SATU baris sebagai fixture/assertion. Perbedaannya tipis di kode, besar di jaminan: kecocokan longgar tidak memberi jaminan keunikan, dan assertion yang diam-diam mengasumsikan keunikan itu flake menunggu waktu.
Ada jebakan kedua yang lebih halus: karakter wildcard bisa muncul di dalam data. JSON sering memuat garis bawah dan tanda persen di konten. Dokumentasi MySQL mengingatkan, "To test for literal instances of a wildcard character, precede it by the escape character" [1]. Jadi meski saya memaksa LIKE tetap dipakai, pola tanpa escape pun bisa melenceng karena data, bukan karena desain.
Kenapa flake ini bertahan lama? Karena collision-nya butuh dua syarat berjalan bersamaan: data cadangan harus memuat nomor bersanding (16, 19), dan urutan baris hasil query harus menempatkan nomor 1 bukan di posisi pertama. Selama dua-duanya kompak, assertion hijau dan semua senang. Uji integrasi memang sering dijalankan di atas dataset yang isinya stabil, jadi jebakan semacam ini tidak tersentuh sampai ada satu perubahan kecil di seed yang menggeser urutan. Flake yang muncul dari perubahan data, bukan perubahan kode, adalah kelas tersendiri yang gampang salah diagnosis: log menunjuk ke query, padahal yang salah ada di pola pencariannya.
Perbaikan bertingkat, dari murah ke benar
Tingkat pertama: perketat predikat jadi exact-value. Bandingkan nilai hasil parsing, bukan potongan string; nomor 1 tetap nomor 1 meski ada nomor 16 di tabel yang sama. Commit perbaikan di repo saya memilih jalur ini dan test kembali deterministik.
Tingkat kedua, kalau pencarian string di JSON memang kebutuhan bisnis: pakai fungsi JSON, bukan LIKE. Manual MySQL menyediakan JSON_SEARCH yang "Returns the path to the given string within a JSON document" [2], dan JSON_CONTAINS untuk cek keanggotaan nilai. Fungsi-fungsi ini paham struktur dokumen; mereka tidak akan menelan "no":16 sebagai "no":1.
Yang saya pegang dari insiden kecil ini: assertion adalah predikat identitas, dan identitas tidak boleh dicari dengan pencarian longgar. Begitu pola pencarian boleh mencocokkan lebih dari satu baris, keputusan "baris mana yang diuji" pindah dari test ke kebetulan. Kebetulan itu bisa bertahan berminggu-minggu, dan persis itu yang membuatnya berbahaya.
Satu catatan praktis untuk yang menemukan pola serupa: jangan langsung membuang test-nya. Assertion yang pernah berbohong itu aset; ia sudah membuktikan mampu menipu, jadi biarkan dia bekerja, hanya dengan predikat yang benar.