Ask a team whether they have tests and the answer is almost always yes. Ask whether they can tell you right now if the current build is safe to put in front of customers, and the answer is often a pause.
That pause is the real problem. Confidence — not coverage — is what a quality practice is supposed to produce.
Why coverage misleads
Coverage measures which lines were executed, not whether anything meaningful was verified. A suite can report a high number while asserting nothing about the workflows that would actually damage the business. Meanwhile the same suite may be slow enough that developers stop running it locally and merge on hope.
Layer one: map risk before writing tests
List the things that must never go wrong. In a financial product that might be duplicated payments, incorrect balances, or a user seeing another customer’s data. In a healthcare product it might be showing the wrong person’s record, or losing a submitted result.
That list is short, and it should receive disproportionate attention: multiple layers of verification, explicit negative tests, and coverage of the failure paths rather than only the happy ones. Everything else can be tested proportionately.
Layer two: make feedback fast enough to use
A suite that takes forty minutes is a suite people route around. Aim for a fast tier that runs on every change in a few minutes, and reserve slower, broader verification for the pipeline. Speed is not a convenience — it determines whether the safety net is used at all.
- Fast unit and component tests covering logic and edge cases
- Contract tests at service and API boundaries, so integration breaks are caught without a full environment
- A small number of end-to-end tests covering the critical journeys only
- Deterministic test data — shared mutable fixtures are the usual root cause of flakiness
Layer three: treat flakiness as a defect
An unreliable test is worse than no test, because it teaches the team to ignore red builds. Track flaky tests explicitly, quarantine them with a deadline and an owner, and fix the underlying determinism problem — usually timing assumptions, shared state or reliance on external systems.
Layer four: verify what functional tests miss
The failures that damage trust are frequently not functional bugs. Performance under realistic load, accessibility, authorisation boundaries and behaviour when a dependency is slow or unavailable all deserve verification — and all are more useful in the pipeline than in an annual report.
Layer five: release progressively
No amount of pre-release testing fully predicts production. Small changes, feature flags, staged rollout and a rehearsed rollback convert "we are confident this works" into "we will find out safely with a limited blast radius". That shift is what allows a team to move faster and break less at the same time.
Layer six: close the loop from production
Every incident should answer one question: what signal would have caught this earlier, and is it worth adding? Sometimes the answer is a test, sometimes an alert, sometimes a design change that removes the failure mode entirely. Over time, that discipline compounds into a suite shaped by real risk rather than by what was convenient to write.
“Release with evidence, not hope.”
