Strategi test untuk cegah flaky QA pada workflow AI coding dimulai dari satu prinsip: jangan memperlakukan kode hasil AI sebagai "lebih cepat jadi" lalu mengurangi disiplin verifikasi. Justru karena AI dapat menghasilkan patch dengan cakupan luas, pola yang inkonsisten, dan asumsi tersembunyi, tim perlu memperketat desain test agar perubahan yang terlihat lolos di lokal tidak berubah menjadi flaky test atau regression di CI.
Untuk tim yang mulai memakai tool AI coding atau model open-source AI dalam workflow development, fokus utamanya bukan hanya menambah jumlah test. Yang lebih penting adalah menempatkan test yang tepat di lapisan yang tepat, membuat input-output lebih eksplisit, menghilangkan sumber nondeterminisme, dan membangun review gate khusus untuk PR AI-assisted. Dengan pendekatan ini, tim kecil pun bisa mengadopsi AI tanpa menjadikan QA makin tidak stabil.
Mengapa workflow AI coding lebih rentan memicu flaky test
Kode hasil AI sering tampak masuk akal, tetapi ada beberapa pola risiko yang membuat test menjadi rapuh:
- Perubahan melebar: satu prompt dapat mengubah banyak file sekaligus, termasuk helper, mapping, validasi, dan test.
- Asumsi implisit: AI bisa menggunakan default tertentu untuk timezone, sorting, format tanggal, urutan field JSON, atau perilaku library.
- Dependensi eksternal tersembunyi: patch dapat menambah panggilan jaringan, akses clock sistem, UUID acak, file system, atau environment variable tanpa disadari reviewer.
- Test ikut diubah agar lolos: AI kadang “memperbaiki” test dengan cara melonggarkan assertion, menambah sleep, atau mengganti verifikasi kuat dengan snapshot besar.
Flaky test biasanya bukan masalah test semata. Ia adalah gejala bahwa sistem verifikasi belum cukup mengontrol sumber nondeterminisme, kontrak perilaku, dan batas tanggung jawab antar-layer.
Strategi inti: risk-based test strategy sebelum menambah coverage
Jika tim baru mulai memakai AI coding, jangan mulai dari target coverage. Mulailah dari risk-based test strategy: area yang paling berisiko harus mendapat test paling deterministik dan paling cepat memberi sinyal ketika perilaku berubah.
Cara memetakan risiko perubahan dari kode AI
Untuk setiap PR AI-assisted, nilai perubahan dengan pertanyaan berikut:
- Apakah menyentuh logic bisnis yang berdampak ke hasil perhitungan, approval, billing, otorisasi, atau workflow penting?
- Apakah menyentuh boundary sistem seperti API pihak ketiga, database, queue, cache, file storage, atau browser?
- Apakah output dipakai oleh sistem lain sehingga perlu kontrak stabil?
- Apakah patch mengubah perilaku waktu, concurrency, retry, timeout, atau pagination?
- Apakah AI menambah branch logic baru tetapi test hanya bertambah sedikit atau terlalu generik?
Dari sini, tim bisa membagi prioritas test:
- High risk: test unit/komponen deterministik + integration test minimal yang membuktikan integrasi utama tetap benar.
- Medium risk: fokus pada kontrak input-output dan edge case.
- Low risk: cukup smoke test dan review manual jika perubahan benar-benar kosmetik.
Aturan praktis: semakin besar kemungkinan AI salah paham terhadap domain, semakin kuat kebutuhan pada test yang sempit, deterministik, dan mudah menjelaskan kegagalan.
Contoh skenario nyata
Misalnya tim memakai AI untuk membantu refactor service pembuatan invoice. AI mengubah fungsi perhitungan diskon, normalisasi mata uang, dan format payload webhook ke sistem akuntansi. Risiko utamanya bukan di rendering UI, tetapi di nilai invoice final dan kontrak payload webhook. Maka prioritas test seharusnya:
- Unit test untuk aturan diskon dan pembulatan.
- Contract test untuk payload webhook.
- Satu integration test untuk jalur sukses dari request sampai job webhook dipicu.
- Bukan menambah screenshot test atau snapshot besar atas seluruh response bila inti risikonya ada di field tertentu.
Gunakan test pyramid, bukan test suite yang berat di ujung atas
Salah satu penyebab flaky QA adalah terlalu banyak mengandalkan end-to-end test untuk memverifikasi perubahan yang sebenarnya bisa dibuktikan lebih stabil di level bawah. Dalam workflow AI coding, masalah ini makin terasa karena patch sering menyentuh banyak titik dan memancing tim menambah test di layer paling lambat.
Penerapan test pyramid yang relevan
- Unit test: verifikasi logic murni, transformasi data, validator, mapper, formatter, policy, dan helper domain.
- Integration/component test: verifikasi interaksi dengan database, message bus, HTTP layer internal, atau serialization penting.
- End-to-end test: hanya untuk alur kritikal yang benar-benar butuh pembuktian lintas boundary.
Kenapa ini efektif untuk kode hasil AI?
- Unit test memberi sinyal cepat jika AI mengubah aturan bisnis secara halus.
- Integration test menangkap mismatch konfigurasi atau kontrak antarkomponen.
- E2E dipakai hemat agar kegagalan tidak terlalu sering disebabkan environment, timing, atau data bersama.
Heuristik pembagian test untuk PR AI-assisted
Untuk setiap perubahan signifikan dari AI, coba targetkan pola seperti ini:
- Tambahkan atau perbarui beberapa unit test spesifik untuk aturan baru atau bug yang diperbaiki.
- Tambahkan satu integration test jika ada boundary penting yang disentuh.
- Tambahkan E2E hanya bila perubahan memengaruhi alur pengguna kritikal dan tidak cukup dibuktikan di layer bawah.
Kesalahan umum adalah menerima PR AI yang lolos karena satu E2E besar, padahal logic internal berubah di banyak cabang dan tidak ada test yang menjelaskan perilaku baru secara presisi.
Buat kontrak input-output eksplisit agar perubahan AI mudah diverifikasi
Semakin ambigu perilaku sistem, semakin mudah AI menghasilkan kode yang “kebetulan lolos” namun salah di kasus pinggir. Karena itu, definisikan kontrak input-output untuk fungsi, endpoint, job, dan event yang penting.
Apa yang dimaksud kontrak input-output
Kontrak tidak harus selalu berarti schema formal. Intinya adalah mendefinisikan:
- Input valid dan invalid.
- Field wajib, optional, dan default.
- Perilaku terhadap null, empty string, duplicate, dan urutan data.
- Output minimum yang dijamin stabil.
- Kode error atau bentuk error yang diharapkan.
Kontrak membuat test lebih tahan terhadap refactor internal karena yang diuji adalah perilaku yang dijanjikan, bukan detail implementasi.
Contoh test kontrak API yang sempit dan berguna
describe('POST /invoices', () => {
it('mengembalikan total dan currency yang konsisten', async () => {
const payload = {
customerId: 'cust-123',
items: [
{ sku: 'A', qty: 2, price: 50000 },
{ sku: 'B', qty: 1, price: 25000 }
],
discountCode: 'NEW10'
};
const res = await request(app)
.post('/invoices')
.send(payload)
.expect(201);
expect(res.body).toMatchObject({
customerId: 'cust-123',
currency: 'IDR'
});
expect(typeof res.body.total).toBe('number');
expect(res.body.total).toBeGreaterThan(0);
expect(res.body.items).toHaveLength(2);
});
});Test di atas tidak mengunci seluruh response, tetapi menjaga bagian kontrak yang benar-benar penting. Ini lebih stabil daripada snapshot penuh yang mudah berubah karena field non-esensial seperti timestamp, metadata, atau urutan internal.
Snapshot boleh dipakai, tetapi harus bijak
Snapshot sering tampak praktis untuk memverifikasi output besar, dan AI juga cenderung mengusulkan snapshot karena cepat dibuat. Namun snapshot adalah salah satu sumber false confidence jika dipakai tanpa batas.
Kapan snapshot layak dipakai
- Struktur output besar tetapi cukup stabil, misalnya AST transform, template rendering tertentu, atau payload yang sudah dinormalisasi.
- Perubahan yang diharapkan memang mudah dilihat melalui diff snapshot.
- Snapshot diperkecil ke bagian yang bermakna, bukan seluruh object mentah.
Kapan snapshot sebaiknya dihindari
- Output mengandung timestamp, UUID, random value, atau urutan field yang tidak stabil.
- Response sangat besar dan reviewer cenderung meng-approve perubahan snapshot tanpa membaca isi.
- Kontrak sebenarnya hanya butuh beberapa assertion eksplisit yang jauh lebih jelas.
Pola snapshot yang lebih aman
it('membentuk payload webhook yang stabil', () => {
const payload = buildWebhookPayload(fixedInvoice);
expect({
event: payload.event,
invoiceId: payload.invoiceId,
total: payload.total,
currency: payload.currency,
lineItems: payload.lineItems
}).toMatchSnapshot();
});Dengan mengekstrak field yang benar-benar penting, snapshot menjadi lebih kecil, lebih mudah direview, dan lebih kecil kemungkinan flaky.
Fixture harus deterministik: kontrol waktu, random, locale, dan data uji
Banyak flaky test berasal dari fixture yang berubah-ubah. Saat AI menambah test baru, masalah ini sering lolos karena test tampak lulus di mesin pembuatnya.
Sumber nondeterminisme yang paling sering
- Waktu sistem:
now(), expiry, tanggal lokal, DST, timezone. - Nilai acak: UUID, token, random suffix, urutan hasil sampling.
- Locale dan format: pemisah desimal, format tanggal, urutan sort string.
- Data bersama: database yang tidak dibersihkan, cache lama, file sementara.
- Concurrency/timing: sleep, retry, job async yang belum selesai saat assertion dijalankan.
Praktik deterministik yang sebaiknya dijadikan standar
- Freeze clock atau inject clock abstraction pada code path penting.
- Seed random jika memang harus memakai generator acak, atau stub generator sama sekali.
- Gunakan fixture eksplisit, jangan mengandalkan factory acak untuk field yang diuji.
- Set timezone dan locale yang konsisten di test runner dan CI.
- Reset state database, cache, queue, dan temp file setiap test atau setiap suite sesuai kebutuhan.
Contoh pendekatan clock injection
function createToken(userId, clock) {
const issuedAt = clock.now();
return {
userId,
issuedAt,
expiresAt: issuedAt + 3600
};
}
it('menghasilkan expiry yang konsisten', () => {
const fixedClock = { now: () => 1700000000 };
const token = createToken('u-1', fixedClock);
expect(token).toEqual({
userId: 'u-1',
issuedAt: 1700000000,
expiresAt: 1700003600
});
});Pola seperti ini sederhana, tetapi sangat efektif untuk mencegah flaky test yang bergantung pada waktu aktual.
Mock dependensi eksternal dengan tujuan yang jelas
Kode AI sering memperkenalkan integrasi tambahan, misalnya memanggil API model, service scoring, webhook, object storage, atau queue. Jika test menyentuh layanan eksternal secara langsung, flaky test hampir pasti meningkat.
Apa yang sebaiknya dimock
- Panggilan HTTP ke service luar.
- Akses object storage atau file system yang tidak perlu untuk skenario itu.
- Message broker atau queue eksternal bila tujuan test bukan menguji broker itu sendiri.
- LLM/API AI provider, baik hosted maupun service internal model gateway.
Apa yang jangan dimock berlebihan
- Logic domain milik sendiri yang justru sedang diuji.
- Serializer, mapper, atau validator internal yang menjadi bagian penting dari kontrak.
- Database di integration test jika tujuan utamanya memang membuktikan query/migrasi/repository bekerja.
Bedakan stub, fake, dan mock
- Stub: mengembalikan data tetap untuk jalur tertentu.
- Fake: implementasi sederhana yang cukup realistis, misalnya in-memory repository.
- Mock: selain memberi respons, juga memverifikasi interaksi tertentu.
Untuk workflow AI coding, fake atau stub yang stabil sering lebih aman daripada mock yang terlalu ketat. Mock berlebihan membuat test rapuh terhadap refactor internal yang sebenarnya tidak mengubah perilaku bisnis.
Contoh boundary wrapper untuk layanan AI
interface CodeSuggestionClient {
reviewDiff(input: { diff: string; rules: string[] }): Promise<{ summary: string; risk: string }>;
}
class FakeCodeSuggestionClient {
async reviewDiff() {
return {
summary: 'Perubahan menyentuh validator invoice',
risk: 'medium'
};
}
}Dengan wrapper seperti ini, test aplikasi tidak tergantung pada latensi, kuota, atau variasi output model. Contract test untuk client wrapper cukup dilakukan terpisah dan jumlahnya sedikit.
Anti-pattern yang sering memicu flaky test saat memakai AI coding
1. Menggunakan sleep untuk menunggu proses async
sleep(1000) bukan sinkronisasi yang andal. Di mesin cepat test lolos, di CI yang sibuk test gagal. Gunakan polling dengan timeout jelas, event hook, atau mekanisme join/await yang benar.
2. Snapshot seluruh response atau DOM tanpa seleksi
Ini membuat review sulit dan perubahan kecil non-esensial terlihat seperti perubahan besar. AI juga mudah “memperbarui snapshot” tanpa memahami apakah perubahan itu benar.
3. Factory data terlalu acak
Data acak berguna untuk eksplorasi, tetapi buruk untuk assertion yang sempit. Jika field penting berubah karena generator acak, test menjadi sulit diulang dan sulit di-debug.
4. Satu test E2E meng-cover terlalu banyak aturan bisnis
Saat gagal, tim tidak tahu apakah masalah ada di UI, API, DB, queue, atau validasi. Pecah bukti ke layer yang lebih kecil.
5. Mock semua hal sampai test tidak lagi realistis
Ini menghasilkan green test palsu. AI-generated code tampak aman, padahal integrasi nyata rusak.
6. PR AI-assisted mengubah kode produksi dan test sekaligus tanpa penjelasan risiko
Jika reviewer tidak tahu perilaku apa yang berubah, test tambahan tidak bisa dinilai dengan benar. Minta ringkasan kontrak yang berubah dan alasan penambahan test.
Checklist review untuk PR AI-assisted
Checklist ini berguna sebagai gate ringan sebelum merge.
Checklist perilaku dan risiko
- Apakah PR menjelaskan apa yang berubah secara perilaku, bukan hanya file yang berubah?
- Apakah area yang disentuh termasuk high risk: billing, auth, permission, data mutation, sync eksternal?
- Apakah ada dependensi baru pada waktu, random, env var, jaringan, file system, atau concurrency?
Checklist kualitas test
- Apakah test baru memverifikasi kontrak yang penting?
- Apakah assertion cukup spesifik dan tidak terlalu longgar?
- Apakah snapshot, jika ada, kecil dan mudah direview?
- Apakah fixture deterministik dan bebas data acak yang tidak perlu?
- Apakah dependensi eksternal diisolasi dengan stub/fake yang stabil?
Checklist anti-flaky
- Tidak ada
sleepsebagai solusi sinkronisasi. - Tidak ada ketergantungan pada urutan data jika urutan bukan kontrak.
- Tidak ada assertion pada timestamp/UUID mentah tanpa kontrol.
- Database, cache, dan queue dibersihkan dengan benar.
- Test dapat berjalan independen dan tidak bergantung urutan eksekusi suite lain.
Verifikasi di CI: jangan hanya menjalankan test, tetapi validasi stabilitas
CI adalah tempat banyak flaky test terungkap karena environment lebih paralel, lebih lambat, atau lebih bersih daripada mesin lokal. Dalam workflow AI coding, CI harus menjadi gate yang memastikan kode lolos bukan karena kebetulan.
Prinsip verifikasi CI yang efektif
- Jalankan suite deterministik lebih dulu: unit dan contract test sebagai sinyal cepat.
- Pisahkan test cepat dan test berat: kegagalan lebih mudah dilokalisasi.
- Konsistenkan environment: timezone, locale, env var, seed, dan service dependency harus jelas.
- Tangkap artefak debug: log, response gagal, screenshot, dan seed data jika relevan.
- Batasi auto-rerun: rerun boleh untuk diagnosis, bukan menyembunyikan flaky test secara permanen.
Contoh langkah CI yang masuk akal
# Contoh alur generik CI
1. Install dependency
2. Set timezone/locale yang konsisten
3. Jalankan lint dan static analysis
4. Jalankan unit test
5. Jalankan contract/integration test
6. Jalankan E2E subset untuk critical path
7. Upload artifact saat gagalJika tim menemukan test yang hanya gagal sesekali, jangan langsung menambah rerun count. Tandai sebagai flaky, kumpulkan gejala, lalu perbaiki sumber nondeterminismenya. Rerun tanpa tindak lanjut hanya menunda masalah.
Sinyal CI yang patut dicurigai pada PR AI-assisted
- Test gagal hanya di CI, terutama terkait waktu atau urutan.
- Durasi suite naik tajam setelah PR yang tampak kecil.
- Snapshot berubah besar padahal deskripsi PR kecil.
- Patch menambah test banyak, tetapi tidak ada test spesifik untuk cabang logic utama.
Contoh skenario implementasi end-to-end yang lebih aman
Bayangkan tim kecil memiliki aplikasi internal dengan fitur generate pull request summary memakai model open-source AI. AI coding tool membantu menambah validasi dan fallback response. Setelah beberapa PR, QA mulai menemukan kegagalan acak.
Penyebab awal
- Test memanggil service model sungguhan melalui HTTP.
- Response disnapshot penuh, termasuk metadata dan latency info.
- Timeout diuji dengan sleep.
- Fixture diff PR dibangkitkan acak.
Perbaikan yang dilakukan
- Buat interface client AI dan pakai fake di mayoritas test aplikasi.
- Tambahkan contract test khusus untuk wrapper HTTP client terhadap service AI.
- Snapshot dipersempit ke field
summary,risk, dansuggestionsyang sudah dinormalisasi. - Clock dan timeout dikontrol melalui dependency injection.
- PR checklist mewajibkan penjelasan apakah perubahan menyentuh boundary eksternal.
Hasil praktisnya bukan sekadar test lebih cepat, tetapi kegagalan menjadi lebih informatif: saat ada error, tim tahu apakah masalah ada di domain logic, wrapper service AI, atau CI environment.
Checklist implementasi bertahap untuk tim kecil
Tim kecil tidak perlu membangun sistem QA yang kompleks sekaligus. Mulai dari langkah yang dampaknya paling besar.
Tahap 1: stabilkan dasar
- Tetapkan aturan bahwa PR AI-assisted harus menjelaskan perubahan perilaku dan risiko.
- Freeze timezone/locale di local test runner dan CI.
- Larangan eksplisit untuk
sleepsebagai sinkronisasi test. - Identifikasi 3-5 area high risk dan pastikan ada unit/contract test untuk area itu.
Tahap 2: rapikan boundary
- Bungkus dependensi eksternal dengan interface atau adapter.
- Gunakan fake/stub untuk service AI, webhook, dan HTTP eksternal di sebagian besar test.
- Pisahkan unit, integration, dan E2E di pipeline CI.
Tahap 3: perketat review dan observability
- Buat template PR khusus AI-assisted dengan checklist anti-flaky.
- Simpan artifact CI saat test gagal.
- Tandai test flaky dan buat backlog perbaikan, jangan dibiarkan menjadi normal.
Tahap 4: naikkan kualitas kontrak
- Definisikan schema atau assertion kontrak untuk endpoint/event penting.
- Kecilkan snapshot lama yang terlalu besar.
- Refactor fixture acak menjadi fixture eksplisit pada test kritikal.
Penutup
Masalah utama dalam workflow AI coding bukan bahwa AI selalu menghasilkan kode buruk, melainkan bahwa AI dapat mempercepat masuknya perubahan yang terlihat benar tetapi membawa nondeterminisme, kontrak kabur, dan test yang sulit dipercaya. Karena itu, strategi test untuk mencegah flaky QA harus berfokus pada risiko, bukan volume.
Untuk tim yang mengadopsi tool AI coding atau open-source AI di development workflow, kombinasi yang paling efektif biasanya sederhana: risk-based testing, test pyramid yang sehat, kontrak input-output yang jelas, snapshot yang hemat, fixture deterministik, mocking boundary eksternal secara disiplin, review checklist khusus PR AI-assisted, dan CI yang memverifikasi stabilitas. Jika ini diterapkan konsisten, tim bisa bergerak cepat tanpa mengorbankan kepercayaan pada test suite.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!