Skip to content

Membekukan kontrak CLI dengan 32 test merah

Adityo Guni Waluyo

Kisah commit 6f5ae82: selftest 32 test sengaja dibikin gagal dulu untuk membekukan kontrak CLI, baru implementasinya ditulis sampai hijau.

Ringkasan

Awalnya penulis kira 32 test merah itu bug, ternyata emang sengaja dibikin gagal sebagai fase RED di TDD. Kontrak CLI dibekukan dulu lewat test pakai varian batch-freeze, bukan TDD kanonik, biar nggak nyimpang pas ngoding. Jadi layar merah itu bukan tanda proyek bermasalah, tapi peta spesifikasi yang tinggal dikejar sampai hijau.

Yang pertama saya lihat bukan kode, tapi merahnya

Dua puluh satu September, saya baru saja push commit 6f5ae82 ke repo internal my-agent. Terminal masih menampilkan hasil run selftest: 32 baris gagal, semua merah. Niat awal saya ngecek kenapa ingest-blueprint.py error. Tebakan pertama: pasti saya salah validasi argumen, atau salah susun logika parser. Saya sempat mikir buang aja runner-nya, toh implementasinya memang belum ada.

Ternyata saya salah baca layar. Kegagalan itu bukan bug. Script ingest-blueprint-selftest.sh memang dirancang supaya semua testnya gagal di titik ini: stub ingest-blueprint.py sengaja cuma mencetak "NOT IMPLEMENTED" ke stderr lalu keluar dengan kode 1. Itu langkah RED di TDD, dan 32 test merah itu justru spesifikasinya, bukannya bukti kegagalan.

Membekukan kontrak, bukan sekadar gagal

Yang saya bekukan bukan kode, tapi kontrak CLI: empat subcommand bernama check, render, init, dan audit, kode keluar tiap kondisi, field output, sampai nama flag. Setelah kontrak dibekukan lewat test, implementasi tinggal mengejar target yang sudah ditulis. Males menyimpang diam-diam jadi percuma, karena tiap penyimpangan langsung kelihatan sebagai test merah.

Prinsipnya sama dengan contract testing [3], yaitu: kontrak antara dua pihak ditangkap lalu disimpan sebagai acuan yang wajib dipatuhi kedua belah pihak. Bedanya, "pihak" di sini bukan dua service, tapi saya yang nulis implementasi hari ini dan saya sendiri tiga minggu lagi yang sudah lupa detail keputusannya.

Fowler mencatat manfaat test-first yang paling sering diremehkan: kita jadi terpaksa mikir soal interface dulu, sebelum detail implementasi [1]. Itu persis yang terjadi. Bagian paling lambat dari pekerjaan ini ternyata bukan menulis logika check-nya, tapi memutuskan format output seperti apa yang enak dipakai. Keputusan semacam itu jauh lebih murah diambil saat semua masih merah.

Varian batch-freeze, bukan TDD kanonik

Saya perlu jujur soal ini. Kent Beck menuliskan Canon TDD: dari daftar skenario, ubah tepat satu item jadi test konkret, bikin lulus, baru lanjut ke item berikutnya [2]. Beliau bahkan memperingatkan risiko rework kalau mengubah seluruh daftar sekaligus. Yang saya lakukan justru itu: 32 skenario langsung jadi test nyata sekaligus.

Ini varian batch-freeze yang saya pilih dengan sadar, bukan TDD buku teks. Tujuannya bukan iterasi mikro, tapi mengunci spesifikasi di commit terpisah sebelum satu baris implementasi ditulis. Risiko rework-nya nyata: kalau nanti test ke-enam memaksa saya mengubah desain, sebagian test lain ikut kebagian revisi. Trade-off-nya saya terima. Untuk kontrak CLI, spesifikasi yang stabil lebih penting daripada ritme iterasi.

Fixture sintetis dan status merah yang bisa dibaca

Runner 557 baris ini tidak pernah menyentuh data nyata. Semua test jalan di direktori mktemp dengan fixture sintetis. Kecepatan jadi bonus, tapi alasan utamanya kejujuran: tidak ada state lama yang bisa bikin test pura-pura hijau. Ketika nanti ada test yang berubah jadi hijau, itu murni karena logika saya benar, bukan karena kebetulan data bekas run sebelumnya. Pola assertion-nya saya adopsi dari dunia bash testing: dengan errexit, tiap baris shell adalah assertion, dan keluar dengan kode 0 berarti lulus [4].

Satu detail kecil yang saya suka: runner menghitung STUB terpisah dari FAIL. Jadi output-nya bukan sekadar "32 gagal", tapi "32 masih stub". Status merah yang bisa dibaca sekilas, tanpa harus menebak mana kegagalan sungguhan dan mana kegagalan yang memang belum selesai dikerjakan.

Langkah berikutnya jelas: implementasi sampai hijau, satu skenario demi satu skenario, persis seperti urutan yang Beck anjurkan. Bedanya, saya mulai dari peta yang sudah lengkap. Saya nggak perlu lagi menebak nama flag atau format output saat nulis kode, karena semuanya sudah tertulis di test-test yang merah itu. Layar penuh merah ternyata bukan tanda proyek bermasalah; itu peta yang harus diikuti.

Sources

[1] Martin Fowler, Test Driven Development bliki, updated 2023-12-11
[2] Kent Beck, Canon TDD, 2023-12-11
[3] PactFlow blog, What is contract testing & how is it used?, updated 2023-09-02
[4] bats-core README, Bash Automated Testing System

Artikel terkait