Flaky test pada continuous integration (CI) sering dipicu oleh kebocoran state database antar-skenario uji. Ketika Test B bergantung pada data sisa dari Test A, perubahan urutan eksekusi menghasilkan kegagalan non-deterministik. Pendekatan perbaikan membutuhkan isolasi state yang ketat, siklus hidup database terukur, dan runner yang terkonfigurasi secara tepat.
1. Deteksi Kebocoran State dengan Randomized Execution
Eksekusi pengujian secara alfabetis atau sekuensial statis menyamarkan dependensi data implisit. Menjalankan test suite dalam urutan acak mengekspos pengujian yang mengasumsikan database kosong atau bergantung pada record dari pengujian sebelumnya.
Aktifkan pengacakan urutan uji pada runner dan simpan nilai seed untuk keperluan reproduksi lokal:
# Go test runner dengan shuffle
go test ./... -shuffle=on
# Pytest dengan plugin pytest-random-order
pytest --random-order-seed=184729
# Jest CLI
jest --randomize --seed=184729Jika kegagalan muncul hanya pada kombinasi seed tertentu, kebocoran state terbukti ada. Gunakan seed tersebut di mesin pengembang untuk mengisolasi query yang gagal membersihkan datanya.
2. Arsitektur Isolasi: Transactional Rollback vs. Ephemeral Database
Pencegahan polusi data dapat ditangani pada dua layer arsitektur: level koneksi database (transaksional) atau level infrastruktur (kontainerisasi).
Pola Transactional Rollback (Savepoint Nesting)
Membungkus setiap pengujian di dalam transaksi dan memanggil ROLLBACK saat teardown:
- Kelebihan: Eksekusi sangat cepat karena mutasi data tidak di-commit ke disk (hanya berada di memory/WAL buffer).
- Kekurangan: Gagal menguji kode yang menggunakan transaksi internal eksplisit (nested transaction memerlukan penanganan savepoint manual), tidak kompatibel dengan pengujian async multithreaded yang membuka koneksi pool berbeda, serta tidak mendukung operasi DDL/migrasi skema.
Pola Ephemeral Database (Testcontainers)
Membuat instance container PostgreSQL, MySQL, atau database target yang benar-benar baru per lifecycle pengujian menggunakan API engine kontainer (misal: Docker via Testcontainers):
- Kelebihan: Paritas penuh dengan environment produksi. Mendukung testing connection pool, transaksi terdistribusi, background worker, dan migrasi skema dinamis.
- Kekurangan: Latensi eksekusi lebih tinggi akibat provisioning kontainer dan I/O disk. Dapat dimitigasi dengan mount volume database ke
tmpfs(in-memory) pada node runner CI.
3. Implementasi Hook Database Reset Deterministik
Ketika menggunakan pola shared-ephemeral container (satu container baru untuk satu batch runner CI), tiap skenario tetap membutuhkan sanitasi data. Menghapus tabel satu per satu lewat ORM lambat dan rentan foreign key constraint error. Gunakan native truncation dengan penonaktifan foreign key check sementara atau mode CASCADE.
Berikut implementasi hook isolasi sebelum tiap test berjalan menggunakan TypeScript dan driver pg:
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.TEST_DATABASE_URL,
});
export async function resetDatabaseState(): Promise<void> {
const client = await pool.connect();
try {
// Dapatkan semua tabel non-sistem di skema public
const query = `
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
AND tablename != 'schema_migrations';
`;
const { rows } = await client.query(query);
if (rows.length === 0) return;
const tableNames = rows
.map((r) => `"${r.tablename}"`)
.join(', ');
// Truncate seluruh tabel dan reset autoincrement sequence
await client.query(`TRUNCATE TABLE ${tableNames} RESTART IDENTITY CASCADE;`);
} finally {
client.release();
}
}
// Setup global test runner hook (misal: Vitest/Jest)
beforeEach(async () => {
await resetDatabaseState();
});Pendekatan TRUNCATE ... RESTART IDENTITY CASCADE menjamin id auto-increment selalu kembali ke 1. Hal ini mencegah pengujian lain gagal akibat ekspektasi ID record statis.
4. Kebijakan Karantina: Hindari Retry Loop Buta
Menambahkan konfigurasi retry: 3 pada pipeline CI adalah antipattern. Kebijakan retry otomatis menyamarkan bug konkurensi, kebocoran memori, deadlocks, dan meningkatkan biaya komputasi CI tanpa menyelesaikan akar masalah.
Terapkan kebijakan karantina (quarantine policy) yang terukur:
- Deteksi dan Tagging: Berikan penanda (misalnya tag
@quarantineatau@flaky) pada test yang gagal secara acak. - Bypass CI Gate: Konfigurasi test runner utama untuk mengabaikan tag karantina agar build pipeline production tidak terblokir:
pytest -m "not quarantine" - Dedicated Quarantine Job: Jalankan suite karantina pada job CI terpisah yang bersifat non-blocking (warning only). Job ini bertugas mengumpulkan log kegagalan, distribusi failure rate, dan seed reproduksi.
- SLA Remediasi: Tetapkan batas waktu perbaikan (misal 14 hari). Jika test di karantina tidak diperbaiki dalam rentang waktu tersebut, hapus pengujian dari repositori untuk menjaga integritas test suite.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!