Kode AI bisa jalan, tapi salah adalah masalah yang makin sering muncul di tim engineering. Masalah utamanya bukan sekadar syntax error atau build gagal. Justru risikonya lebih halus: kode berhasil compile, endpoint merespons 200, UI tampil normal, tetapi logika bisnisnya keliru, edge case bocor, atau integrasi berperilaku tidak sesuai kontrak.

Dalam praktiknya, verifikasi kode hasil AI tidak bisa berhenti di “tes hijau”. Tim perlu workflow yang memisahkan apakah kode dapat dijalankan dari apakah hasilnya benar. Artikel ini membahas strategi verifikasi yang praktis untuk tim: level pengujian yang tepat, cara membuat oracle test untuk aturan bisnis, pola menambah regression test dari bug nyata, checklist review PR untuk kode AI, dan langkah penerapan di CI agar perubahan lolos karena benar, bukan karena kebetulan.

Konteks yang sering dibahas di komunitas developer, termasuk di artikel-artikel dev.to, adalah hal yang sama: AI membantu menulis kode lebih cepat, tetapi verifikasi tetap pekerjaan manusia dan sistem engineering. Kecepatan generasi kode tidak mengurangi kebutuhan akan desain test yang baik.

Mengapa kode AI sering tampak benar padahal salah?

Model AI unggul menghasilkan pola yang mirip kode valid. Karena itu, ia sering mampu membuat implementasi yang:

  • lolos parser dan compiler,
  • memenuhi bentuk API atau struktur proyek,
  • terlihat konsisten dengan gaya codebase,
  • menangani alur utama yang umum.

Namun, AI tidak benar-benar memahami niat bisnis seperti engineer yang membaca domain secara penuh. Akibatnya, kesalahan yang sering muncul bukan pada bentuk kode, tetapi pada kebenaran perilaku. Contohnya:

  • diskon dihitung sebelum pajak, padahal aturan bisnis mengharuskan sebaliknya,
  • status pesanan berubah ke paid saat webhook diterima, padahal seharusnya menunggu verifikasi signature dan nominal,
  • frontend menampilkan total dari data cache lama karena efek samping state tidak dipertimbangkan,
  • validasi hanya memeriksa format input, bukan konsistensi antar-field.

Ini sebabnya indikator seperti “build sukses”, “endpoint jalan”, atau “snapshot tidak berubah” belum cukup untuk menyimpulkan implementasi benar.

Bedakan compile/run vs correct

Langkah pertama dalam workflow verifikasi adalah memisahkan tiga level sinyal:

  1. Compile/Type-check: kode valid secara sintaks dan tipe.
  2. Run: kode dapat dijalankan tanpa crash pada skenario tertentu.
  3. Correct: hasilnya sesuai aturan bisnis, kontrak, dan ekspektasi sistem.

Banyak tim berhenti di level kedua. Padahal justru masalah terbesar kode AI ada di level ketiga. Untuk membuat perbedaannya jelas, lihat contoh backend sederhana berikut.

Contoh: perhitungan total pesanan

function calculateTotal(items, discountPercent, taxPercent) {
  const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0);
  const discounted = subtotal - (subtotal * discountPercent / 100);
  const taxed = discounted + (discounted * taxPercent / 100);
  return Math.round(taxed);
}

Kode di atas bisa:

  • berhasil dijalankan,
  • menghasilkan angka,
  • terlihat masuk akal,
  • lolos tes dangkal seperti “hasilnya harus lebih besar dari nol”.

Tetapi bila aturan bisnis sebenarnya adalah pajak dihitung dari subtotal sebelum diskon, implementasi itu salah. Ini contoh klasik kode yang jalan tetapi tidak benar.

Karena itu, tim perlu menguji properti kebenaran, bukan hanya keberhasilan eksekusi.

Workflow verifikasi: petakan level test dengan jelas

Strategi yang praktis adalah memetakan pengujian berdasarkan jenis risiko. Jangan membebani satu level test untuk semua hal. Gunakan kombinasi berikut.

1. Unit test: cek logika kecil dengan ekspektasi eksplisit

Unit test cocok untuk fungsi murni, transformasi data, aturan hitung, parser, validator, dan decision logic. Untuk kode hasil AI, level ini penting karena banyak bug terjadi di cabang logika yang terlihat sepele.

Contoh unit test untuk aturan total pesanan:

test('pajak dihitung dari subtotal sebelum diskon', () => {
  const items = [{ price: 10000, qty: 2 }];
  const result = calculateTotal(items, 10, 11);

  // subtotal = 20000
  // pajak = 2200
  // diskon = 2000
  // total = 20200 bila aturan bisnis: subtotal + pajak - diskon
  expect(result).toBe(20200);
});

Test ini lebih kuat daripada sekadar memeriksa tipe hasil atau memastikan hasil tidak nol. Ia menjadi oracle untuk aturan bisnis tertentu.

2. Integration test: cek interaksi antar-komponen

Integration test diperlukan saat bug tidak muncul pada fungsi kecil, tetapi pada batas antarkomponen: service ke database, handler ke queue, API ke cache, atau frontend ke backend mock.

Contoh risiko yang sering muncul pada kode AI:

  • query benar secara sintaks tetapi salah join sehingga data duplikat,
  • transaction tidak mencakup seluruh operasi penting,
  • serializer menghapus field yang dibutuhkan klien,
  • mapping status antara internal enum dan payload eksternal tertukar.

Integration test sebaiknya memverifikasi perilaku nyata, misalnya:

  • record yang tersimpan setelah request,
  • event yang dipublish setelah transaksi sukses,
  • efek idempotensi saat request yang sama dikirim dua kali.

3. Contract test: cek kesesuaian antar layanan

Bila sistem Anda memakai API internal atau pihak ketiga, contract test membantu memastikan struktur request/response benar secara semantik, bukan sekadar field-nya ada.

Yang perlu diverifikasi antara lain:

  • field wajib dan field opsional,
  • format nilai seperti tanggal, mata uang, dan status,
  • arti kode status HTTP,
  • perilaku saat field baru muncul atau field lama kosong.

Ini penting untuk kode AI karena model sering menebak bentuk payload yang terlihat wajar tetapi tidak sesuai kontrak nyata.

4. End-to-end test: cek alur penting, bukan semua kombinasi

E2E test berguna untuk memastikan alur bisnis kritis bekerja dari ujung ke ujung: login, checkout, pembayaran, pembuatan invoice, atau reset password. Namun, jangan menjadikannya satu-satunya lapisan verifikasi karena E2E cenderung lambat, rapuh, dan sulit mendiagnosis akar masalah.

Pilih beberapa skenario yang benar-benar penting untuk bisnis. Tujuannya bukan menutup semua kombinasi, tetapi memberi sinyal bahwa sistem tetap bekerja pada alur inti.

Membuat test oracle untuk aturan bisnis

Masalah paling umum pada kode AI adalah test yang terlalu dangkal. Misalnya:

  • “response status harus 200”,
  • “array tidak kosong”,
  • “komponen berhasil render”,
  • “fungsi dipanggil sekali”.

Semua itu berguna, tetapi belum cukup untuk membuktikan kebenaran logika. Yang dibutuhkan adalah test oracle: sumber ekspektasi yang jelas untuk menentukan output benar atau salah.

Ciri oracle test yang baik

  • Berbasis aturan domain, bukan kebetulan implementasi.
  • Memiliki input dan output yang dapat dihitung manual.
  • Mengunci edge case penting, misalnya pembulatan, batas minimum, duplikasi request, zona waktu, atau status transisi.
  • Tidak bergantung pada detail internal yang mudah berubah.

Contoh aturan bisnis yang cocok dijadikan oracle

  • voucher hanya berlaku untuk kategori produk tertentu,
  • refund parsial tidak boleh melebihi total yang sudah dibayar,
  • pengguna dengan peran tertentu tidak boleh melihat data lintas organisasi,
  • pesanan COD tidak boleh langsung masuk status paid,
  • tanggal jatuh tempo yang jatuh di hari libur harus digeser ke hari kerja berikutnya.

Teknik menyusun oracle test

  1. Tulis aturan bisnis dalam bahasa natural yang singkat.
  2. Ubah menjadi tabel input-output.
  3. Pilih beberapa contoh normal dan beberapa contoh batas.
  4. Pastikan ekspektasi dihitung dari aturan, bukan menyalin hasil implementasi.

Contoh tabel sederhana untuk diskon:

Kasus 1: subtotal 20000, diskon 10%, pajak 11% => 20200
Kasus 2: subtotal 0, diskon 10%, pajak 11% => 0
Kasus 3: subtotal 10000, diskon 100%, pajak 11% => sesuai aturan bisnis
Kasus 4: item campuran, hanya kategori A dapat diskon => hitung per item

Dengan cara ini, tim tidak hanya memverifikasi “fungsi bekerja”, tetapi “fungsi menghasilkan keputusan yang benar”.

Tambah regression test dari bug nyata

Salah satu cara paling efektif meningkatkan kualitas kode AI adalah menjadikan setiap bug nyata sebagai test permanen. Saat bug ditemukan di staging atau production, jangan berhenti pada perbaikan kode. Tambahkan regression test yang gagal sebelum perbaikan dan lulus sesudahnya.

Workflow regression yang praktis

  1. Reproduksi bug dengan input sekecil mungkin.
  2. Tulis test yang menangkap perilaku salah tersebut.
  3. Pastikan test gagal pada versi buggy.
  4. Perbaiki implementasi.
  5. Pastikan test kini lulus dan tetap ada di suite.

Keuntungan pendekatan ini:

  • bug yang sama tidak mudah kembali,
  • tim membangun perpustakaan kasus domain nyata,
  • kode AI berikutnya dipaksa menghormati pelajaran dari insiden sebelumnya.

Contoh regression test dari bug webhook pembayaran

test('webhook tidak menandai paid bila signature invalid', async () => {
  const payload = {
    orderId: 'ORD-123',
    amount: 50000,
    status: 'settlement'
  };

  const response = await request(app)
    .post('/payments/webhook')
    .set('x-signature', 'invalid')
    .send(payload);

  expect(response.status).toBe(401);
  const order = await findOrderById('ORD-123');
  expect(order.status).not.toBe('paid');
});

Test seperti ini jauh lebih bernilai daripada test yang hanya memverifikasi endpoint merespons tanpa error.

Cegah flaky test: kontrol data, waktu, dan dependensi eksternal

Sinyal palsu bukan hanya datang dari test yang dangkal, tetapi juga dari test yang tidak deterministik. Flaky test membuat tim sulit membedakan bug nyata dari kebisingan. Dalam konteks kode AI, ini berbahaya karena implementasi yang salah bisa lolos ketika suite test sendiri tidak stabil.

1. Kontrol data uji

Gunakan data fixture atau factory yang eksplisit. Hindari data acak tanpa alasan jelas. Jika perlu random, tetapkan seed agar hasil dapat direproduksi.

Praktik yang baik:

  • buat data minimum yang relevan dengan kasus,
  • reset database per test atau per suite secara konsisten,
  • hindari ketergantungan pada urutan data yang tidak dijamin.

2. Kontrol waktu

Banyak bug logika terkait waktu: masa berlaku token, cutoff harian, timezone, retry window, atau jatuh tempo. Jangan biarkan test bergantung pada jam sistem nyata jika tidak perlu.

Gunakan abstraksi waktu, misalnya:

  • wrapper seperti Clock.now(),
  • fitur fake timers pada framework test,
  • nilai tanggal yang diinjeksi ke service.

Dengan demikian, Anda bisa menguji skenario seperti “tepat tengah malam UTC” atau “voucher kedaluwarsa 1 detik lagi” secara konsisten.

3. Kontrol dependensi eksternal

Jangan menggantungkan mayoritas test pada jaringan nyata, layanan pihak ketiga, atau API yang datanya berubah. Untuk integration dan contract test, gunakan stub, mock server, atau environment sandbox yang stabil.

Perlu dibedakan:

  • Mock internal untuk mempercepat verifikasi komponen lokal.
  • Stub/contract test untuk memvalidasi interaksi dengan layanan lain.
  • Smoke test terbatas ke sistem nyata untuk memastikan integrasi masih hidup.

Semua lapisan ini saling melengkapi. Mengandalkan sistem nyata untuk semua test biasanya lambat dan rapuh; mem-mock semuanya juga berisiko menutupi mismatch kontrak.

Contoh kasus sederhana: backend dan web

Kasus backend: endpoint checkout

Misalkan AI menghasilkan endpoint checkout yang:

  • menghitung total,
  • menerapkan voucher,
  • menyimpan order,
  • mengirim event ke queue.

Verifikasi yang masuk akal:

  • Unit test untuk aturan voucher, pajak, pembulatan, dan ongkir.
  • Integration test untuk memastikan order tersimpan dengan total yang benar dan status awal yang benar.
  • Contract test untuk payload event order.created.
  • E2E test untuk alur checkout berhasil dan checkout gagal karena voucher tidak valid.

Bug yang sering lolos bila verifikasi lemah:

  • voucher berlaku pada item yang seharusnya dikecualikan,
  • queue terkirim sebelum transaksi database commit,
  • field customerId hilang di event meski endpoint tetap 200.

Kasus web: komponen keranjang belanja

Di frontend, AI mungkin menghasilkan komponen yang render dengan baik, tetapi state sinkronisasi salah. Misalnya, total harga tidak diperbarui saat quantity berubah karena perhitungan hanya dilakukan saat mount pertama.

Verifikasi yang lebih kuat:

  • Unit test untuk fungsi selector atau reducer total harga.
  • Component/integration test untuk interaksi tambah/kurang item dan efeknya pada total.
  • E2E test untuk alur update cart sampai checkout summary.

Yang perlu dihindari adalah test UI yang hanya memeriksa elemen ada di DOM tanpa memastikan nilai bisnis yang tampil benar.

Checklist review PR untuk kode AI

Review PR untuk kode hasil AI sebaiknya lebih sistematis daripada review biasa. Fokusnya bukan hanya gaya penulisan, tetapi pembuktian perilaku.

Pertanyaan inti saat review

  • Apakah PR ini hanya membuktikan kode jalan, atau juga membuktikan hasilnya benar?
  • Aturan bisnis mana yang benar-benar diuji?
  • Apakah ada edge case penting yang belum dikunci?
  • Apakah test memverifikasi output domain, bukan sekadar status sukses?
  • Apakah ada bagian yang tampak “masuk akal” tetapi belum diverifikasi terhadap spesifikasi?

Checklist praktis

  • Tujuan perubahan jelas: bugfix, fitur, atau refactor.
  • Ada referensi aturan bisnis: tiket, spesifikasi, contoh input-output, atau acceptance criteria.
  • Test sesuai risiko: logika kecil diuji di unit, integrasi diuji di integration, kontrak diuji di contract.
  • Ekspektasi test spesifik: nilai akhir, transisi status, efek samping, payload event.
  • Negative case ada: invalid input, unauthorized, duplicate request, race condition dasar, atau mismatch payload.
  • Regression test ada bila PR memperbaiki bug.
  • Tidak terlalu bergantung pada mock sampai kehilangan perilaku nyata.
  • Tidak ada test yang lolos hanya karena assertion lemah, misalnya hanya cek truthy, non-null, atau status 200.
  • Dependensi waktu dan data dikontrol agar tidak flaky.
  • Observability dipikirkan: logging, metric, atau audit trail untuk membantu debugging bila bug tetap lolos.

Penerapan di CI: jangan biarkan perubahan lolos karena tes hijau palsu

CI yang efektif bukan sekadar menjalankan test. CI harus memaksa kualitas sinyal. Artinya, pipeline dirancang agar perubahan tidak lolos hanya karena kebetulan semua test hijau.

Susun stage CI berdasarkan tingkat kepercayaan

Contoh urutan yang umum dan masuk akal:

  1. Static checks: lint, format, type-check.
  2. Unit tests: cepat, jumlah banyak, fokus logika.
  3. Integration tests: dengan database atau service lokal yang dikontrol.
  4. Contract tests: untuk API/event penting.
  5. Smoke/E2E terbatas: untuk alur bisnis kritis.

Pemisahan ini membantu diagnosis. Jika integration gagal, tim tidak perlu menebak-nebak apakah masalahnya ada di unit logic atau di wiring antarkomponen.

Tambahkan gate yang relevan untuk kode AI

  • Wajib test baru untuk perubahan logika bisnis.
  • Wajib regression test untuk bugfix.
  • Blok merge bila hanya snapshot atau assertion dangkal yang berubah pada area sensitif.
  • Tandai test flaky sebagai insiden kualitas, bukan masalah kecil yang diabaikan.

Contoh kebijakan tim yang realistis

Untuk perubahan yang dibuat atau dibantu AI pada area pembayaran, autentikasi, harga, dan izin akses:

  • minimal satu oracle test per aturan bisnis yang disentuh,
  • minimal satu negative case,
  • integration test bila ada interaksi database atau service lain,
  • review manual oleh engineer domain owner.

Kebijakan seperti ini lebih berguna daripada aturan generik “coverage harus naik”. Coverage bisa tinggi, tetapi tetap tidak membuktikan kebenaran logika.

Gunakan failure output yang membantu investigasi

CI yang baik juga memberi output yang memudahkan debugging:

  • diff expected vs actual yang jelas,
  • seed random bila ada property-based test,
  • log request/response yang disanitasi,
  • artifact seperti screenshot atau trace untuk test UI.

Tujuannya agar saat kode AI salah, tim cepat memahami mengapa salah, bukan sekadar tahu bahwa pipeline gagal.

Kesalahan umum yang perlu dihindari

  • Menganggap generated tests otomatis berkualitas. Test buatan AI juga bisa dangkal dan sekadar mencerminkan implementasi yang salah.
  • Mengandalkan snapshot untuk logika bisnis. Snapshot berguna untuk struktur output tertentu, tetapi lemah untuk aturan domain yang harus dihitung secara eksplisit.
  • Menguji implementasi internal, bukan perilaku. Ini membuat test rapuh dan kurang berguna saat refactor.
  • Terlalu banyak mock. Sistem terlihat hijau, tetapi integrasi nyata tidak pernah benar-benar diverifikasi.
  • Tidak menambahkan test setelah bug ditemukan. Tanpa regression, bug lama mudah kembali dalam variasi baru.

Penutup

Kode AI bisa jalan, tapi salah karena sinyal “berhasil dijalankan” tidak sama dengan “benar secara logika”. Cara mengatasinya bukan dengan menolak AI, melainkan dengan memperketat verifikasi: bedakan compile/run dari correct, petakan level test sesuai risiko, tulis oracle test untuk aturan bisnis, simpan pelajaran dari bug nyata sebagai regression test, dan bangun CI yang menilai kualitas sinyal, bukan sekadar jumlah tes hijau.

Jika tim Anda mulai memakai AI untuk implementasi harian, titik kritisnya bukan pada kecepatan menulis kode, tetapi pada disiplin membuktikan perilaku. Semakin mudah kode dihasilkan, semakin penting sistem verifikasi yang membatasi kesalahan logika sebelum sampai ke pengguna.