Strategi testing saat AI menulis kode harus berangkat dari satu prinsip sederhana: tim tidak boleh mempercayai implementasi hanya karena kodenya terlihat masuk akal. Jika AI dipakai untuk menghasilkan fungsi, endpoint, query, atau refactor, yang harus dikendalikan manusia adalah perilaku sistem melalui spesifikasi, test case, dan workflow verifikasi yang konsisten.
Masalah utamanya bukan sekadar bug biasa. AI sering menghasilkan kode yang tampak rapi tetapi menyelipkan asumsi tersembunyi, memperluas interpretasi requirement, atau mengubah perilaku kecil yang tidak langsung terlihat saat review. Karena itu, pendekatan yang aman bukan “cek cepat lalu merge”, melainkan ubah requirement menjadi test yang dapat diverifikasi, tambah invariant untuk area kritis, pisahkan test deterministik dari integrasi, dan pasang gate CI agar tim tidak mendapat false confidence.
Mengapa verifikasi lebih penting daripada membaca kode
Review kode tetap penting, tetapi saat AI terlibat, membaca implementasi saja sering tidak cukup. Ada beberapa alasan praktis:
- Kode terlihat meyakinkan. Nama variabel, struktur fungsi, dan komentar bisa rapi, tetapi logikanya tetap salah di kasus tepi.
- AI cenderung mengisi celah requirement. Jika spesifikasi tidak tegas, model akan “menebak” perilaku yang menurutnya wajar.
- Refactor jadi terlalu murah. Karena perubahan bisa dibuat cepat, risiko regresi meningkat jika pengaman testing lemah.
- Review manusia terbatas. Reviewer biasanya kuat pada pola arsitektur dan domain, tetapi lemah jika harus memvalidasi banyak cabang logika kecil tanpa test yang jelas.
Prinsip yang berguna di sini mirip dengan gagasan control the ideas, not the code: jangan menjadikan implementasi sebagai sumber kebenaran. Sumber kebenaran harus ada pada requirement yang bisa diuji.
Area risiko utama saat AI membantu menulis kode
1. Asumsi tersembunyi
Ini adalah sumber bug paling umum. Misalnya:
- AI menganggap input selalu terurut.
- AI menganggap field tertentu selalu ada.
- AI menganggap timeout atau retry tidak perlu.
- AI menganggap format tanggal, timezone, atau locale tertentu.
Masalahnya, asumsi ini sering tidak tertulis di kode maupun deskripsi perubahan. Jika test hanya memeriksa jalur utama, bug baru terlihat di produksi.
2. Flaky test
Flaky test memberi sinyal yang berbahaya: pipeline tampak hijau atau merah secara acak, sehingga tim kehilangan kepercayaan pada suite test. Saat AI dipakai untuk menambah banyak test secara cepat, flaky test sering muncul dari:
- Ketergantungan pada waktu sistem.
- Penggunaan jaringan atau service eksternal tanpa isolasi.
- Urutan eksekusi yang tidak stabil.
- Shared state antar test.
- Asersi terhadap data yang nondeterministik.
Flaky test bukan sekadar gangguan. Ia menurunkan kualitas sinyal CI dan membuat bug nyata tertutup oleh noise.
3. Snapshot yang rapuh
Snapshot bisa berguna untuk output besar, misalnya struktur JSON atau hasil render UI. Namun snapshot yang terlalu luas sering berubah karena detail yang tidak penting. Akibatnya:
- Review berubah menjadi “update snapshot” tanpa memahami dampaknya.
- Regresi semantik lolos karena perubahan visual tampak kecil.
- Tim terbiasa menerima diff besar yang tidak dibaca.
Jika AI menghasilkan komponen atau serializer, snapshot penuh sering memberi rasa aman palsu.
4. Regresi saat refactor cepat
Salah satu keuntungan AI adalah mempercepat refactor: rename, ekstraksi fungsi, pemecahan modul, migrasi pola, atau normalisasi kode berulang. Tetapi refactor yang cepat juga meningkatkan risiko perubahan perilaku tak disengaja, terutama di area yang:
- punya logika bisnis bercabang,
- terhubung ke database atau cache,
- mengandung aturan kompatibilitas mundur,
- dipakai banyak consumer.
Semakin cepat perubahan dibuat, semakin penting test yang memverifikasi kontrak sistem.
Workflow inti: ubah requirement menjadi test yang bisa dijalankan
Pola yang paling aman adalah memaksa setiap perubahan berangkat dari perilaku yang dapat diamati. Alur sederhananya:
- Tulis atau perjelas requirement dalam bentuk aturan konkret.
- Ubah aturan itu menjadi acceptance test.
- Tambahkan property/invariant test untuk hal yang harus selalu benar.
- Pisahkan test deterministik dari test integrasi.
- Gunakan CI gate agar merge hanya terjadi jika sinyal test valid.
Dengan alur ini, AI boleh membantu menulis implementasi, bahkan membantu menulis test awal, tetapi tim tetap memegang definisi benar-salahnya.
Contoh praktis: dari requirement ke acceptance test
Misalkan tim backend/web punya endpoint checkout yang menerapkan kupon diskon. Requirement bisnisnya:
- Kupon hanya berlaku jika belum kedaluwarsa.
- Diskon tidak boleh membuat total menjadi negatif.
- Kupon tidak bisa dipakai dua kali oleh user yang sama jika aturannya satu kali pakai.
- Respons harus tetap menyertakan subtotal, discount, dan final_total.
Jangan mulai dari prompt “buatkan fungsi apply coupon”. Mulailah dari acceptance test yang mendeskripsikan perilaku endpoint.
describe('POST /checkout', () => {
it('menerapkan kupon valid ke total belanja', async () => {
const response = await request(app)
.post('/checkout')
.send({
userId: 'u1',
items: [
{ sku: 'A', price: 100000, qty: 1 },
{ sku: 'B', price: 50000, qty: 1 }
],
coupon: 'HEMAT10'
});
expect(response.status).toBe(200);
expect(response.body).toMatchObject({
subtotal: 150000,
discount: 15000,
final_total: 135000
});
});
it('menolak kupon yang kedaluwarsa', async () => {
const response = await request(app)
.post('/checkout')
.send({
userId: 'u1',
items: [{ sku: 'A', price: 100000, qty: 1 }],
coupon: 'OLDPROMO'
});
expect(response.status).toBe(400);
expect(response.body.error_code).toBe('COUPON_EXPIRED');
});
it('tidak pernah menghasilkan total negatif', async () => {
const response = await request(app)
.post('/checkout')
.send({
userId: 'u1',
items: [{ sku: 'A', price: 10000, qty: 1 }],
coupon: 'POTONG100RB'
});
expect(response.status).toBe(200);
expect(response.body.final_total).toBe(0);
});
});Acceptance test seperti ini bermanfaat karena:
- berbicara dalam bahasa perilaku bisnis, bukan detail implementasi,
- tetap relevan walaupun fungsi internal di-refactor,
- memaksa tim menutup ambiguitas sebelum AI menulis terlalu banyak kode.
Pola penulisan acceptance test yang efektif
- Fokus pada kontrak eksternal. Status code, struktur respons, efek samping penting, dan error code lebih berharga daripada memeriksa fungsi internal dipanggil berapa kali.
- Satu skenario, satu alasan gagal. Jangan gabungkan terlalu banyak aturan dalam satu test.
- Gunakan data yang bermakna. Hindari angka acak yang sulit dibaca reviewer.
- Tuliskan edge case eksplisit. Misalnya nilai nol, input kosong, duplikasi, batas maksimum, timezone, atau data yang tidak terurut.
Tambah property test atau invariant test untuk menangkap asumsi tersembunyi
Acceptance test menangkap contoh konkret. Namun AI sering gagal pada ruang input yang lebih luas. Di sinilah property test atau invariant test berguna: alih-alih memeriksa satu contoh, test memverifikasi aturan yang harus selalu benar.
Untuk kasus checkout tadi, beberapa invariant yang layak diuji:
final_total >= 0subtotal = sum(price * qty)discount <= subtotal- Urutan item tidak boleh mengubah hasil akhir.
- Input yang sama harus menghasilkan output yang sama jika dependensi eksternal dikendalikan.
describe('checkout invariants', () => {
it('hasil tidak bergantung pada urutan item', () => {
const itemsA = [
{ sku: 'A', price: 10000, qty: 2 },
{ sku: 'B', price: 5000, qty: 1 }
];
const itemsB = [
{ sku: 'B', price: 5000, qty: 1 },
{ sku: 'A', price: 10000, qty: 2 }
];
const resultA = calculateCheckout(itemsA, { type: 'percent', value: 10 });
const resultB = calculateCheckout(itemsB, { type: 'percent', value: 10 });
expect(resultA).toEqual(resultB);
});
it('tidak pernah menghasilkan final_total negatif', () => {
for (const subtotal of [0, 1, 1000, 100000]) {
const result = applyDiscount(subtotal, { type: 'fixed', value: 999999 });
expect(result.final_total).toBeGreaterThanOrEqual(0);
expect(result.discount).toBeLessThanOrEqual(subtotal);
}
});
});Jika tim memakai framework yang mendukung generator data, property-based testing bisa diperluas. Tetapi bahkan tanpa library khusus, daftar invariant sederhana sudah sangat efektif untuk mencegah asumsi tersembunyi.
Catatan: Jangan mengejar property test untuk semua modul. Prioritaskan area dengan aturan bisnis padat, banyak kombinasi input, atau bug historis yang sering muncul di kasus tepi.
Pisahkan test deterministik dari test integrasi
Salah satu sumber false confidence adalah semua jenis test dicampur dalam satu lapisan. Saat itu terjadi, test yang seharusnya cepat dan tegas ikut menjadi lambat, rapuh, dan sulit didiagnosis.
Test deterministik
Test ini harus:
- berjalan lokal tanpa jaringan,
- tidak bergantung pada waktu nyata,
- tidak berbagi state,
- menghasilkan hasil yang sama di setiap eksekusi.
Contohnya: validator, formatter, pricing engine, parser, mapper, policy, dan aturan domain murni. Inilah lapisan terbaik untuk menerima banyak bantuan AI, karena perilakunya bisa dipagari dengan test yang tajam.
Test integrasi
Test ini memverifikasi kontrak antar komponen, misalnya:
- API dengan database,
- worker dengan queue,
- service dengan cache,
- frontend dengan backend mock atau environment khusus.
Test integrasi memang penting, tetapi harus dipilih dengan sengaja. Tujuannya bukan menutup semua kombinasi, melainkan memastikan batas antar sistem tidak rusak.
Aturan praktis pemisahan
- Deterministik untuk logika, integrasi untuk kontrak.
- Mock secukupnya di unit/domain layer, bukan untuk menyembunyikan perilaku penting.
- Gunakan fake atau in-memory dependency hanya jika semantik tetap representatif.
- Jangan menguji framework secara berlebihan. Uji aturan bisnis dan kontrak yang memang milik sistem Anda.
Pemisahan ini penting saat AI menghasilkan test. Model sering cenderung membuat test yang memanggil banyak lapisan sekaligus karena itu terlihat “realistis”, padahal hasilnya lambat dan sulit di-maintain.
Hindari snapshot yang rapuh
Snapshot bukan musuh, tetapi penggunaannya harus sempit dan sadar risiko. Gunakan snapshot jika:
- output panjang dan stabil secara semantik,
- perubahan memang perlu direview sebagai diff,
- struktur yang diuji adalah kontrak penting.
Hindari snapshot jika yang berubah hanya detail presentasi atau field incidental. Untuk banyak kasus backend/web, asersi eksplisit lebih aman.
Kurang baik: snapshot seluruh respons API yang berisi timestamp, ID acak, metadata debug, dan urutan field yang tidak penting.
Lebih baik: asersi hanya pada field kontrak utama.
expect(response.body).toMatchObject({
status: 'success',
subtotal: 150000,
discount: 15000,
final_total: 135000
});
expect(response.body.items).toHaveLength(2);
expect(response.body).not.toHaveProperty('internal_debug');Jika snapshot tetap diperlukan, kecilkan cakupannya:
- snapshot hanya bagian output yang stabil,
- normalisasi timestamp, UUID, atau urutan elemen lebih dulu,
- gabungkan dengan asersi semantik, bukan snapshot saja.
Pasang gate CI untuk mencegah false confidence
CI yang hanya menjalankan test lalu menampilkan status hijau belum cukup. Saat AI mempercepat produksi kode, CI harus menjadi mekanisme penyaring, bukan formalitas.
Gate minimum yang disarankan
- Lint dan type check untuk menangkap inkonsistensi dasar.
- Test deterministik wajib lulus dan harus cepat.
- Test integrasi terpilih wajib lulus untuk area kontrak utama.
- Flaky test detection atau setidaknya karantina test yang tidak stabil.
- Larangan merge jika snapshot berubah tanpa review manusia pada modul sensitif.
- Coverage diff opsional, tetapi jangan jadikan persentase coverage sebagai satu-satunya sinyal kualitas.
Contoh workflow CI yang masuk akal
steps:
- install dependencies
- run lint
- run typecheck
- run deterministic tests
- run integration tests for changed modules
- upload test report
- require human review if contract tests or snapshots changedPoin pentingnya bukan nama tool, melainkan urutan pengambilan keputusan. Jika deterministic test gagal, pipeline sebaiknya berhenti cepat. Jika integration test gagal, laporan harus cukup jelas untuk menunjukkan modul mana yang terdampak.
Debugging sinyal CI yang buruk
- Jika test kadang gagal, tandai sebagai flaky dan keluarkan dari gate utama sampai diperbaiki.
- Jika suite terlalu lambat, pisahkan smoke test dari full regression.
- Jika reviewer sering menyetujui snapshot besar tanpa membaca, kecilkan ukuran snapshot dan wajibkan asersi eksplisit.
- Jika bug lolos walau coverage tinggi, periksa apakah coverage hanya mengeksekusi baris tanpa memverifikasi perilaku penting.
Pola implementasi untuk tim backend dan web
1. Requirement template sebelum AI menulis implementasi
Gunakan format singkat seperti ini di tiket atau pull request:
- Perilaku utama: apa yang harus dilakukan sistem.
- Input penting: data valid, tidak valid, batas bawah/atas.
- Output/efek samping: respons, perubahan database, event, cache, redirect.
- Invariant: apa yang tidak boleh dilanggar.
- Non-goal: hal yang sengaja tidak dicakup perubahan.
Template ini mencegah AI “mengisi kekosongan” dengan asumsi sendiri.
2. Minta AI membantu menulis test dulu, lalu review test dengan ketat
Daripada langsung meminta implementasi, lebih aman meminta draft test lebih dulu. Tetapi test hasil AI tetap harus direview manusia untuk memastikan:
- skenario sesuai requirement nyata,
- asersi memeriksa perilaku yang benar,
- tidak ada mock berlebihan,
- tidak ada ketergantungan waktu/jaringan yang membuat flaky.
Jika test sudah baik, implementasi bisa diganti berkali-kali tanpa kehilangan kontrol.
3. Simpan bug produksi sebagai regression test permanen
Setiap bug yang lolos ke staging atau produksi sebaiknya menghasilkan minimal satu regression test. Ini sangat penting ketika AI sering dipakai untuk refactor, karena bug lama mudah muncul kembali dalam bentuk berbeda.
4. Jaga kontrak API dan data shape
Untuk backend/web, banyak regresi muncul bukan karena logika inti salah, tetapi karena kontrak output berubah diam-diam. Lindungi dengan:
- contract test pada endpoint penting,
- validasi schema request/response,
- test kompatibilitas untuk field yang dipakai consumer lama,
- normalisasi serializer agar perubahan incidental tidak menyebar.
Kesalahan umum yang perlu dihindari
- Menganggap test yang banyak berarti aman. Yang penting adalah kualitas sinyal, bukan jumlah file test.
- Mengandalkan snapshot penuh sebagai pengganti asersi semantik.
- Mencampur unit, integration, dan end-to-end dalam satu jalur lambat.
- Meminta AI membuat mock kompleks yang justru menyembunyikan bug kontrak.
- Mengabaikan bug kecil saat refactor karena “test masih hijau”. Bisa jadi test belum menangkap perilaku penting.
- Menggunakan coverage sebagai target utama. Coverage berguna sebagai indikator tambahan, bukan bukti kebenaran.
Checklist strategi testing saat AI menulis kode
- Requirement ditulis sebagai perilaku yang bisa diuji.
- Ada acceptance test untuk skenario utama dan error penting.
- Ada invariant/property test untuk aturan yang harus selalu benar.
- Test deterministik dipisahkan dari test integrasi.
- Waktu, random, dan jaringan diisolasi agar test tidak flaky.
- Snapshot dibatasi hanya untuk output yang benar-benar stabil dan penting.
- Setiap bug produksi penting dikonversi menjadi regression test.
- CI menghentikan merge jika sinyal inti gagal.
- Perubahan kontrak API, schema, atau snapshot sensitif wajib direview manusia.
Penutup
Strategi testing saat AI menulis kode bukan tentang menolak otomatisasi, melainkan memastikan otomatisasi tidak mengambil alih definisi kebenaran. Jika tim mengendalikan spesifikasi, acceptance test, invariant, dan gate CI, maka implementasi boleh berubah cepat tanpa mengorbankan perilaku sistem.
Intinya sederhana: biarkan AI membantu menulis kode, bahkan membantu membuat draft test, tetapi jangan biarkan ia menentukan apa yang benar. Yang harus menentukan adalah test yang lahir dari requirement yang jelas, deterministik, dan bisa diverifikasi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!