Test Parallelization and Flaky Test Detection
A test suite with 10,000 tests takes 2 hours to run sequentially. Distributed across 50 machines, it takes 2-3 minutes. Test parallelization is a solved problem in principle but creates real distributed systems challenges: test isolation, work distribution, and flaky tests that fail intermittently and poison the signal.
Work Distribution#
Naively split 10,000 tests evenly: 200 per machine. The problem: tests have wildly varying durations. 200 fast unit tests complete in 10 seconds. 200 slow integration tests take 5 minutes. The slowest machine determines total runtime.
Better: use historical timing data. Record the duration of each test from previous runs. Pack tests into bins of equal estimated total duration using a bin-packing heuristic. Each machine gets a similar amount of estimated work. The fastest machines finish early and pull additional work from a shared queue.
Test Isolation#
Parallel tests share the same CI machine or cluster. Isolation failures cause spurious failures:
Database state: test A writes a row; test B expects no rows in that table. Solution: each test runs against its own schema, created fresh and torn down after. Transaction-per-test with rollback is faster than schema-per-test.
Port conflicts: two tests bind port 8080. Solution: allocate dynamic ports or use network namespaces.
Flaky Test Detection#
A flaky test passes sometimes and fails sometimes with the same code. In a distributed test run, any single flake blocks the pipeline. At 10,000 tests, even a 0.1% flake rate means 10 flaky failures per run.
Detection: track pass/fail history per test across many runs. A test that fails in fewer than 5% of runs on the same commit is flaky. Tag it; don’t block the pipeline on it; file a ticket.
Automatic retry: on test failure, retry the test 2-3 times on a different machine. If it passes on retry, it’s flaky. Report both outcomes. Only fail the build if the test fails on all retries.
Quarantine: flaky tests that fail repeatedly get quarantined — moved to a separate non-blocking suite. The pipeline passes; the quarantine suite runs separately and alerts the owning team. This prevents flaky tests from blocking shipping while keeping visibility on the failures.
Root Causes of Flakiness#
Time-dependent assertions: assert result.timestamp == now(). Fails when the test runs slowly.
Race conditions in async code: testing that an event has been processed without waiting for it.
Resource contention: test relies on a shared external service that’s sometimes slow.
Order-dependent state: test passes only when run after another specific test that sets up state.
At Oracle#
Oracle’s integration test suite for a network function management platform had 340 tests taking 4 hours sequentially. After distributing across 20 parallel workers using time-based binning, runtime dropped to 18 minutes. Flaky test rate was 8% initially — 27 tests that failed intermittently due to hardcoded ports and shared database schemas. After enforcing dynamic port allocation and per-test transaction rollback, flaky rate dropped to under 0.3%.
What I’m Learning#
Test parallelization requires solving three problems: work distribution (bin-pack by historical duration), isolation (per-test database state, dynamic ports), and flakiness (detect, quarantine, retry). Getting all three right is what makes parallel CI feel reliable rather than randomly broken.
Have you dealt with a flaky test problem at scale, and what detection or quarantine strategy actually worked?