Flaky test adalah tes yang kadang lulus dan kadang gagal tanpa perubahan kode yang relevan. Masalah ini menggerus kepercayaan pada CI, memperlambat merge, dan sering membuat bug nyata tertutup oleh noise. Jika tim Anda ingin memperbaikinya tanpa proyek besar yang berat, format weekend challenge bisa dipakai sebagai latihan terarah: pilih beberapa flaky test, ukur failure rate, isolasi penyebabnya, lalu perbaiki workflow verifikasi sebelum merge.

Artikel ini menggunakan tren komunitas seperti DEV Weekend Challenge hanya sebagai inspirasi format kerja: tantangan kecil, fokus, dan dapat dievaluasi pada awal pekan. Tujuannya bukan mengikuti tren, melainkan membuat perbaikan kualitas test suite menjadi rutin, terukur, dan cukup ringan untuk tim kecil.

Mengapa flaky test perlu diperlakukan sebagai masalah engineering

Flaky test bukan sekadar gangguan kecil. Dalam CI, ia punya biaya nyata:

  • False alarm: pipeline merah padahal produk tidak rusak.
  • Waktu investigasi terbuang: engineer men-debug gejala acak, bukan akar masalah.
  • Kepercayaan menurun: tim mulai menganggap hasil CI tidak penting.
  • Regresi lolos: bug nyata bisa diabaikan karena semua orang terbiasa melihat kegagalan acak.

Karena itu, flaky test sebaiknya diperlakukan seperti masalah reliabilitas sistem: diukur, diklasifikasikan, diprioritaskan, lalu ditangani dengan kebijakan yang jelas.

Definisi flaky test dan cara membedakannya dari bug nyata

Apa yang disebut flaky test

Test disebut flaky bila hasilnya tidak deterministik untuk input, environment, dan commit yang sama. Dalam praktik, pola yang sering muncul adalah:

  • Gagal di CI tetapi lulus di rerun tanpa perubahan.
  • Gagal hanya pada runner tertentu atau saat beban tinggi.
  • Gagal jika dijalankan paralel, tetapi lulus jika dijalankan sendiri.
  • Gagal pada waktu tertentu, zona waktu tertentu, atau urutan test tertentu.

Flaky test vs bug nyata

Perbedaan utamanya ada pada reproducibility dan konsistensi kondisi:

  • Bug nyata biasanya bisa direproduksi dengan kondisi yang sama dan failure-nya stabil.
  • Flaky test cenderung berubah-ubah tanpa perubahan kode atau input fungsional.

Namun, jangan terlalu cepat memberi label “flaky”. Kadang test tampak acak karena ada race condition di aplikasi, dan itu berarti ada bug nyata di sistem produksi. Jadi, proses klasifikasi harus menjawab pertanyaan ini: apakah ketidakstabilan datang dari test harness, environment, atau perilaku aplikasi yang memang salah?

Praktik klasifikasi yang aman

  1. Jalankan ulang test yang gagal beberapa kali pada commit yang sama.
  2. Jalankan test secara terisolasi dan dalam suite penuh.
  3. Bandingkan hasil lokal vs CI.
  4. Lihat apakah ada dependensi waktu, jaringan, urutan, shared state, atau paralelisme.
  5. Jika kegagalan mengindikasikan pelanggaran kontrak bisnis yang konsisten, anggap sebagai bug nyata sampai terbukti sebaliknya.

Catatan: Label “flaky” tidak boleh menjadi alasan untuk menunda bug produksi. Jika test kadang gagal karena race condition pada kode aplikasi, memperbaiki test saja tidak cukup; perilaku aplikasinya juga harus diperbaiki.

Sumber umum flaky test di backend, frontend, dan CI

Backend

  • Ketergantungan waktu: penggunaan now(), timeout ketat, atau perbandingan timestamp yang terlalu presisi.
  • State database bocor: data dari test sebelumnya tidak dibersihkan dengan benar.
  • Race condition: worker async, queue, atau event belum selesai saat assertion dilakukan.
  • Port/resource conflict: service test memakai resource yang sama saat paralel.
  • Ketergantungan jaringan: memanggil API eksternal nyata dalam test.

Frontend

  • Selector rapuh: bergantung pada teks, urutan DOM, atau class yang berubah.
  • Timing UI: assertion dijalankan sebelum render, hydration, atau request selesai.
  • Animation/transitions: elemen belum stabil saat di-click atau diperiksa.
  • Shared browser state: cookie, local storage, atau cache tidak dibersihkan antar test.
  • Mock yang tidak konsisten: respons API mock berbeda antar skenario.

CI dan environment

  • Runner lambat atau variatif: CPU throttling membuat timeout terlalu agresif.
  • Eksekusi paralel: test saling mempengaruhi lewat filesystem, DB, atau service bersama.
  • Urutan test berubah: ada test yang diam-diam bergantung pada test lain.
  • Konfigurasi tidak identik: env var, locale, timezone, atau seed berbeda.
  • Provisioning tidak siap: service dependency dianggap siap padahal health check belum benar-benar lulus.

Format Weekend Challenge yang efektif untuk tim kecil

Alih-alih mengumumkan “minggu ini kita bersihkan semua test”, lebih realistis membuat tantangan kecil yang bisa selesai dalam 1-2 sesi. Format ini cocok untuk tim kecil karena fokus, punya target jelas, dan hasilnya bisa dibahas cepat di awal pekan.

Tujuan challenge

  • Mengidentifikasi 3-5 test paling sering gagal.
  • Membedakan flaky test dari bug produk.
  • Menambahkan guardrail di workflow sebelum merge.
  • Mengurangi noise tanpa menyembunyikan kegagalan nyata.

Contoh agenda 2 hari

  1. Sabtu pagi: tarik data gagal 2-4 minggu terakhir dari CI.
  2. Sabtu siang: kelompokkan menurut penyebab: timing, data leakage, network, order dependency, resource contention.
  3. Sabtu sore: pilih 1-2 perbaikan cepat dan 1 kebijakan workflow.
  4. Minggu: implementasi retry terbatas, quarantine policy, dan logging failure rate.
  5. Senin: review hasil, putuskan mana yang diperbaiki, mana yang dihapus, mana yang diisolasi sementara.

Aturan main agar challenge tidak jadi kosmetik

  • Jangan menambah retry global untuk semua test.
  • Jangan memindahkan semua test gagal ke quarantine tanpa tiket perbaikan.
  • Setiap test yang di-quarantine harus punya owner, alasan, dan tanggal review.
  • Ukur perubahan dengan metrik, bukan perasaan.

Verification workflow sebelum merge

Workflow verifikasi yang baik mengurangi peluang flaky test menghambat merge sekaligus mencegah regresi lolos. Intinya adalah memisahkan sinyal penting dari noise.

Struktur workflow yang disarankan

  1. Fast checks: lint, type check, unit test deterministik.
  2. Required integration tests: test penting untuk kontrak API, database, auth, atau logic lintas modul.
  3. Optional atau non-blocking suite: E2E lambat, browser matrix, atau test yang masih dalam proses stabilisasi.
  4. Post-merge verification: suite lebih luas untuk mendeteksi regresi yang tidak ekonomis dijalankan penuh di setiap PR.

Poin pentingnya: jangan mencampur semua jenis test ke satu gerbang merge tanpa prioritas. Test yang belum cukup stabil sebaiknya tidak langsung dijadikan blocker, tetapi tetap diamati ketat sampai diperbaiki.

Kapan merge harus diblokir

  • Test deterministik pada area kritis gagal konsisten.
  • Failure dapat direproduksi dan menunjukkan perilaku aplikasi yang salah.
  • Kegagalan terkait keamanan, transaksi, data integrity, atau auth.

Kapan boleh lanjut dengan mitigasi

  • Kegagalan berasal dari test yang sudah diketahui flaky dan terdaftar resmi.
  • Test tersebut masuk quarantine dengan aturan review yang aktif.
  • Suite utama lain tetap hijau dan tidak ada indikasi bug nyata.

Retry yang aman: kapan membantu, kapan berbahaya

Retry bisa berguna untuk mengurangi false negative, tetapi hanya aman jika dipasang terbatas dan transparan. Retry yang salah justru menyamarkan bug nyata.

Prinsip retry yang aman

  • Terapkan pada test tertentu, bukan seluruh pipeline secara global.
  • Catat percobaan pertama sebagai sinyal kegagalan, walau percobaan berikutnya lulus.
  • Batasi jumlah retry kecil, misalnya satu kali untuk test yang memang sudah diinvestigasi.
  • Jangan retry test yang gagal karena assertion fungsional yang jelas.
  • Gabungkan retry dengan logging environment dan artefak debug.

Mengapa retry global berbahaya

  • Menutupi regresi intermiten yang sebenarnya nyata.
  • Membuat failure rate terlihat lebih baik dari kondisi sebenarnya.
  • Menambah durasi CI tanpa meningkatkan kualitas test.

Jika Anda memakai retry, pikirkan retry sebagai alat observasi sementara, bukan solusi permanen.

Test quarantine tanpa kehilangan kontrol kualitas

Test quarantine berarti memindahkan test yang diketahui flaky ke jalur terpisah agar tidak memblokir merge utama, sambil tetap dipantau sampai diperbaiki atau dihapus. Ini sering lebih realistis daripada membiarkan satu test acak merusak throughput seluruh tim.

Kapan test layak di-quarantine

  • Sudah ada bukti kegagalan intermiten pada commit yang sama.
  • Penyebabnya sudah dipahami secara umum, tetapi perbaikannya belum bisa dilakukan segera.
  • Test tersebut bukan satu-satunya penjaga untuk area kritis.

Aturan quarantine yang sehat

  • Beri label atau folder khusus.
  • Catat issue/ticket yang menaut ke test tersebut.
  • Tentukan owner.
  • Tentukan tanggal evaluasi ulang.
  • Pastikan ada alternatif coverage jika test kritis dipindahkan.

Kapan test harus diperbaiki atau dihapus

Perbaiki jika test memverifikasi perilaku penting dan bisa dibuat deterministik dengan usaha masuk akal. Hapus jika test tidak lagi relevan, menduplikasi coverage lain, atau hanya memeriksa implementasi internal yang sering berubah tetapi tidak memberi nilai bisnis.

Aturan praktis: test yang sering gagal, mahal dirawat, dan tidak menjaga kontrak penting biasanya lebih baik dihapus atau diganti dengan test yang levelnya lebih tepat.

Pencatatan failure rate yang cukup untuk tim kecil

Tim kecil tidak perlu platform observability test yang rumit untuk memulai. Yang dibutuhkan hanyalah konsistensi dalam mencatat sinyal berikut:

  • Nama test
  • Suite atau jenis test
  • Commit/PR
  • Status percobaan pertama
  • Status setelah retry
  • Runner/job
  • Durasi
  • Kategori dugaan penyebab

Metrik yang bisa dipantau tim kecil

  • Failure rate per test: berapa kali gagal dibanding total eksekusi.
  • Pass-after-retry rate: indikator kuat adanya flakiness.
  • Top 10 noisy tests: test yang paling sering mengganggu CI.
  • Mean time to stabilize: waktu dari identifikasi sampai perbaikan/quarantine/hapus.
  • Pipeline disruption count: berapa PR tertahan karena flaky test.
  • Quarantine backlog: jumlah test dalam quarantine dan usianya.

Tujuan metrik bukan membuat dashboard cantik, melainkan memudahkan keputusan prioritas.

Contoh alur di GitHub Actions

Contoh berikut menunjukkan pemisahan antara suite utama dan suite quarantine. Ini bukan template universal, tetapi cukup untuk menggambarkan alur yang aman.

name: ci

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  test-main:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup runtime
        run: ./scripts/setup-ci.sh
      - name: Run deterministic test suite
        run: ./scripts/run-tests.sh --exclude-tag=quarantine
      - name: Upload test reports
        if: always()
        run: ./scripts/upload-test-report.sh

  test-quarantine:
    runs-on: ubuntu-latest
    continue-on-error: true
    steps:
      - uses: actions/checkout@v4
      - name: Setup runtime
        run: ./scripts/setup-ci.sh
      - name: Run quarantined tests
        run: ./scripts/run-tests.sh --tag=quarantine
      - name: Upload quarantine reports
        if: always()
        run: ./scripts/upload-test-report.sh --quarantine

Pola di atas bekerja karena:

  • test-main tetap menjadi gerbang merge untuk test yang dianggap cukup stabil.
  • test-quarantine tetap dijalankan agar tim tidak kehilangan visibilitas.
  • Artefak laporan tetap diunggah meskipun job gagal.

Jika framework test Anda mendukung retry per-test, letakkan konfigurasi retry hanya pada tag atau file yang memang sudah teridentifikasi flaky, dan pastikan laporan menyimpan hasil percobaan pertama.

Contoh alur di GitLab CI

stages:
  - test
  - quarantine

main_tests:
  stage: test
  script:
    - ./scripts/setup-ci.sh
    - ./scripts/run-tests.sh --exclude-tag=quarantine
  artifacts:
    when: always
    paths:
      - test-reports/

quarantine_tests:
  stage: quarantine
  script:
    - ./scripts/setup-ci.sh
    - ./scripts/run-tests.sh --tag=quarantine
  allow_failure: true
  artifacts:
    when: always
    paths:
      - test-reports/

Di GitLab CI, allow_failure: true dapat dipakai untuk suite quarantine agar visibilitas tetap ada tanpa memblokir alur utama. Tetapi disiplin proses tetap dibutuhkan: test quarantine harus dievaluasi rutin, bukan dibiarkan menumpuk.

Teknik debugging yang sering efektif

1. Bekukan sumber nondeterminisme

  • Set timezone dan locale eksplisit.
  • Gunakan seed acak yang tercatat.
  • Mock waktu bila test sensitif terhadap clock.

2. Jalankan test berulang pada commit yang sama

Tujuannya bukan hanya memastikan test “kadang gagal”, tetapi mengukur pola. Misalnya, apakah gagal hanya saat paralel atau hanya pada runner tertentu.

3. Isolasi state

  • Reset database per test atau per suite dengan strategi yang konsisten.
  • Bersihkan file sementara, cache, cookie, local storage, dan queue.
  • Pastikan port dan resource tidak dipakai bersama tanpa isolasi.

4. Tambahkan observabilitas minimal

  • Simpan screenshot/video untuk E2E jika perlu.
  • Simpan log aplikasi dan log service dependency.
  • Catat durasi langkah yang rentan timeout.

5. Cari order dependency

Jalankan test yang gagal secara terpisah, lalu jalankan setelah test lain yang dicurigai. Jika hasil berubah, besar kemungkinan ada kebocoran state antar test.

Checklist implementasi Weekend Challenge

  1. Ambil data kegagalan CI 2-4 minggu terakhir.
  2. Pilih 3-5 test paling noisy.
  3. Labeli: bug nyata, flaky terduga, atau belum jelas.
  4. Uji ulang pada commit yang sama, baik terisolasi maupun dalam suite penuh.
  5. Kelompokkan akar masalah: waktu, state, network, paralelisme, environment, selector, order dependency.
  6. Buat daftar test yang boleh retry terbatas.
  7. Terapkan quarantine untuk test yang memang perlu dipisah sementara.
  8. Pastikan suite utama sebelum merge hanya berisi test yang cukup stabil dan kritis.
  9. Upload artefak laporan secara konsisten.
  10. Catat failure rate, pass-after-retry rate, dan quarantine backlog.
  11. Tetapkan owner dan tenggat review untuk setiap test quarantine.
  12. Putuskan dengan tegas: perbaiki, ganti, atau hapus test yang tidak memberi nilai.

Kesalahan umum yang perlu dihindari

  • Mengandalkan retry sebagai solusi utama: ini menutupi masalah.
  • Mengkarantina terlalu banyak test: kualitas sinyal CI turun drastis.
  • Tidak menyimpan data percobaan pertama: Anda kehilangan indikator flakiness.
  • Menganggap semua flakiness berasal dari test: bisa jadi ada bug concurrency di aplikasi.
  • Tidak punya owner: test quarantine akan menumpuk dan dilupakan.

Penutup

Strategi Weekend Challenge untuk menjinakkan flaky test di CI bekerja bukan karena formatnya sedang populer, tetapi karena ia memaksa tim fokus pada perbaikan kecil yang terukur. Dengan definisi yang jelas, verification workflow sebelum merge, retry yang aman, quarantine yang disiplin, dan pencatatan failure rate, tim kecil pun bisa mengurangi noise CI tanpa mengorbankan deteksi regresi.

Mulailah dari sedikit data, beberapa test paling bermasalah, dan satu kebijakan sederhana yang benar-benar dijalankan. Dalam konteks flaky test, konsistensi biasanya lebih berharga daripada inisiatif besar yang tidak pernah selesai.