Testing in the Pipeline
Pipelines turn testing into a gate. Learn the test pyramid — unit, integration, end-to-end — how to enforce coverage, how to handle flaky tests without training teams to ignore red, and when contract testing beats brittle e2e suites.
What is this?
Automated testing in pipelines runs suites at every commit and fails the build on any regression. The test pyramid puts many fast unit tests at the base, fewer integration tests in the middle, and a small number of end-to-end tests at the top. Coverage gates enforce a minimum line coverage, and contract testing verifies API compatibility between services cheaply.
Why do you need it?
- A failing test in the pipeline is the earliest, cheapest place to catch a regression.
- Fast tests give feedback within minutes, keeping developers productive.
- The test pyramid balances speed against confidence — too many e2e tests make the suite slow and brittle.
- Coverage gates prevent silent regressions where new code ships entirely untested.
- Contract tests catch breaking API changes between services without running full environments.
Mental model
A quality inspector in a factory
The test pyramid
The inspector checks every bolt quickly (unit tests), samples how parts fit together (integration tests), and only at the end tests the finished product once (e2e). If you only tested finished products you would find defects far too late and at far too high a cost.
How it works
- Unit tests exercise a single function or class in isolation, running in milliseconds across thousands of cases.
- Integration tests verify components cooperate — database, cache, message queue — using real or lightweight substitutes.
- End-to-end tests drive the whole system through its real interfaces, proving the user-facing flow works.
- A coverage tool reports what fraction of lines are executed and fails the build below a configured threshold.
- Contract tests pin the shape of an API so producer and consumer teams detect breaking changes without a full environment.
The test pyramid: many fast unit tests, fewer integration tests, a few e2e tests.
Key commands
npm testRun the unit test suite defined in package.json.
pytest --cov=src --cov-fail-under=90Run Python tests and fail if line coverage drops below 90%.
go test ./... -raceRun all Go tests with the race detector enabled.
npx playwright testRun the end-to-end browser test suite.
npm run test:contractVerify service-to-service API contracts with the contract test suite.
Examples
A coverage-gated test run in CI
$ npm test -- --coveragePASS src/calc.test.js ✓ add returns the sum (2 ms) ✓ subtract returns the difference (1 ms) Coverage: 92.4% statements | 90% branches | 95% functions | 91% linesERROR: Coverage for lines (91%) does not meet global threshold (95%)npm ERR! Test run failed — build stoppedThe suite passes, but the coverage gate fails because line coverage is 91% against a 95% threshold. The pipeline stops and the commit is blocked until the missing paths are covered.
In production
- Pull requests run unit and integration suites; merges to main run the slower e2e suite.
- Coverage gates in CI prevent untested new code from shipping, often with patch-level thresholds.
- Contract tests run in each service's pipeline so producers and consumers find breaking API changes before deploy.
- Flaky tests are quarantined and auto-retried once; persistent flakes block the suite until fixed.
- Slow test tiers move to nightly runs so the commit path stays fast.
Best practices
- Keep unit tests hermetic — no network, no shared state, no sleeping.
- Run the fast suite on every commit and reserve e2e tests for merges and releases.
- Enforce coverage as a gate but set realistic thresholds that reflect the codebase.
- Treat flaky tests as bugs: quarantine them immediately and fix or delete them.
- Give tests deterministic fixtures and random seed handling so results never depend on ordering.
Common mistakes
- Building a huge e2e suite that takes an hour and fails randomly — teams stop watching it.
- Gaming coverage with tests that assert nothing, inflating the percentage without protecting behavior.
- Ignoring flaky tests until everyone habitually clicks rerun.
- Letting tests touch production services or real user data.
Common issues
Test passes locally but fails only in CIEnvironment drift — different Node/Python version, missing fixture, or network access in CI.
Match the CI runtime locally, pin versions in lockfiles, and make tests hermetic so they need no network.
Coverage gate fails despite tests passingThe coverage threshold is set above the suite's actual reach, or new code is untested.
Inspect the coverage report, add tests for the uncovered lines, then confirm the percentage passes.
A test is intermittently red (flaky)Race conditions, timeouts, shared state or test ordering.
Quarantine the test, reproduce with repeated runs and a fixed seed, and fix the root cause rather than rerunning blindly.
Debugging tips
- Rerun the failing test locally in isolation — if it passes, suspect ordering or shared state.
- Use test filters to run only the failing module and check the traceback before blaming the suite.
- Inspect the coverage report's uncovered lines to see exactly which branch the gate flagged.
- Add a marker to intentionally flaky tests so they are excluded from blocking runs while being fixed.
Interview checkpoint
Hands-on lab
- Write source code and matching assertions by hand
- Run test gates using shell exit codes
- Enforce a coverage-style minimum with a count check
- Simulate a flaky test and handle it deterministically
Gate a Build on Tests and Coverage
30 min · intermediate
Quick quiz
Which tests belong at the base of the test pyramid?
A test that passes locally but fails in CI indicates a difference between the environments.
A suite is 90% covered by assertions but the build just failed on a regression in an untested edge case. What is the best next step?
Cheat sheet
unitFast, isolated, run on every commitintegrationComponents against real dependenciese2eFull system, reserved for merges/releasescontractAPI compatibility between servicescoverage gateFail below a line-coverage thresholdnpm testJS unit suitepytest --cov=src --cov-fail-under=90Python + coverage gatego test ./... -raceGo with race detectionnpx playwright testBrowser e2ejest --runInBandSerialize tests to debug ordering