Pendahuluan

Strategi layered testing memungkinkan tim mengidentifikasi regresi dan flaky tests secara sistematis dengan menjalankan lapisan test yang berbeda sesuai konteks. Dalam artikel ini, kita langsung membahas definisi masing-masing lapisan (unit, contract, integration, end-to-end), cara mengkombinasikannya dalam pipeline, aturan gating, serta pendekatan praktis menjaga stabilitas rilis.

Tujuan utama adalah memastikan setiap perubahan kode diuji di tingkatan yang paling efektif dulu—unit untuk logika kecil, contract untuk API/kontrak antar layanan, integration untuk dependensi eksternal, dan end-to-end untuk alur pengguna—tanpa mengorbankan kecepatan feedback.

Lapisan Testing dan Kombinasi Ideal dalam Pipeline

1. Unit tests

Unit test mengisolasi fungsi atau kelas tunggal tanpa dependensi eksternal. Idealnya dijalankan lokal dan di pipeline awal (linting/test). Fokus pada deterministik, biasanya menggunakan mocking minimal untuk memastikan logika bisnis tetap stabil.

2. Contract tests

Contract test memvalidasi spesifikasi antar layanan atau API publik. Gunakan framework seperti Pact atau schema validation untuk backend, dan contract testing service worker/GraphQL jika frontend menunggu public schema. Jalankan segera setelah unit test sukses, karena kegagalan menunjukkan mismatch spesifikasi.

3. Integration tests

Integration tests mengecek interaksi dengan database, cache, atau layanan eksternal. Jalankan di environment staging dengan data terkontrol. Pastikan database reset sebelum setiap suite, dan gunakan test doubles untuk layanan yang berisiko.

4. End-to-end (E2E)

E2E mensimulasikan jalur pengguna lengkap. Karena mahal, jadwalkan setelah integration sukses dan selektif—misalnya suite smoke untuk rilis cepat dan suite lengkap untuk nightly build.

Menggabungkan Lapisan

Atur pipeline bertingkat: unit → contract → integration → E2E. Setiap lapisan mensyaratkan gating (sekitar satu lapisan). Misalnya, jika contract test gagal, pipeline berhenti sebelum integration. Pastikan bagian frontend dan backend menjalani masing-masing lapisan sesuai peran (frontend punya contract ke backend, backend punya integration ke database). Jangan gabungkan terlalu banyak lapisan dalam satu job untuk menjaga isolasi.

Aturan Gating dan Penyaringan Regresi

Gating berdasarkan lapisan

  • Pre-commit/local: lint + unit cepat.
  • Pre-merge/pull: unit + contract + sebagian integration (mocked). Contract failure menghentikan merge untuk mencegah contract drift.
  • Post-merge/pre-release: full integration + E2E smoke. Ini memberi sinyal bahwa regresi dari dependensi utama tertangkap sebelum rilis.

Contoh job pipeline (YAML)

stages:
  - unit
  - contract
  - integration
  - e2e

unit_tests:
  stage: unit
  script: pytest tests/unit

contract_tests:
  stage: contract
  script: pact-verifier --provider-url=http://api:8080
  needs: [unit_tests]

integration_tests:
  stage: integration
  script: pytest tests/integration
  needs: [contract_tests]

smoke_e2e:
  stage: e2e
  script: npm run test:smoke
  needs: [integration_tests]

Gunakan needs agar job berikutnya hanya berjalan jika lapisan sebelumnya sukses. Ini mencegah regresi masuk berantai.

Mitigasi Flaky Tests

Isolasi

Karantina test yang flaky dengan menjalankannya pada environment khusus (misalnya job flaky-investigation) sehingga tidak menahan pipeline utama. Terapkan dependency injection dan fixture explicit untuk menghindari test bergantung pada state global.

Retry Selektif

Gunakan retry hanya pada test yang sudah diidentifikasi flaky (misalnya max 2 retry). Implementasi dengan atribut, seperti:

@pytest.mark.flaky(reruns=1)
def test_external_api():
    assert client.call().status == 200

Jangan aktifkan retry massal karena bisa menyembunyikan bug nyata.

Observabilitas

Catat metadata ketika test gagal: log request/response, screenshot E2E, trace ID. Ini mempercepat debugging dan membantu menentukan apakah kegagalan karena environment atau kode.

Menjaga Pencegahan Regresi

Tracking Test Debt

Gunakan board atau label test debt untuk fitur yang kurang coverage atau flaky. Jadwalkan refactor test bersama backlog teknis agar tidak terus menumpuk.

Review Coverage

Review PR tidak lengkap tanpa melihat coverage delta. Jika testing lapisan tertentu diabaikan, minta penulis menambahkan kasus atau melakukan mock tambahan. Fokus pada branch critical paths.

Branching Policy

Gunakan branch yang memisahkan perubahan besar (feature branch) dan pastikan pipeline lengkap berjalan sebelum merge ke main. Terapkan protected branch dengan required status check untuk semua lapisan utama.

Workflow Verifikasi Pra-Merge

  1. Create branch dari main dan lakukan unit + contract test lokal.
  2. Buka PR dengan deskripsi lapisan yang diuji dan highlight perubahan kunci.
  3. Pipeline otomasi: unit → contract → sampel integration. Jobs gagal membatalkan merge.
  4. Reviewer memeriksa coverage serta review log flaky. Jika job integration gagal karena dependency eksternal, verifikasi observability logs sebelum retry.
  5. Setelah PR disetujui dan seluruh job selesai, merge ke main. Tambahkan run nightly pipeline untuk E2E lengkap sebelum rilis.

Kenapa workflow ini bekerja

Dengan testing bertingkat, regressi ditangkap di level paling ekuivalen—unit menangkap logika individu, contract menjaga konsistensi antar layanan, dll. Gating memastikan tidak ada langkah melewati kegagalan, sementara mitigation flaky dan pencegahan regression menjaga pipeline tetap sehat.

Kesimpulan

Strategi layered testing menjadikan pipeline backend/frontend lebih tahan terhadap flaky tests dan regresi. Kepatuhan pada aturan gating, monitoring test debt, dan workflow pra-merge yang disiplin menjamin stabilitas rilis. Pastikan setiap lapisan menambah nilai unik, dan perbaiki flaky tests sebelum menjadi beban proses.