100% Test Coverage Is a Red Flag, Not a Badge
One hundred percent test coverage is a red flag, not a badge. Martin Fowler put it bluntly: he'd "be suspicious of anything like 100% — it would smell of someone writing tests to make the coverage numbers happy, but not thinking about what they are doing." When the number becomes the goal, teams write tests for getters, config glue, and generated code — effort spent exactly where bugs don't live.
What coverage actually measures
Coverage tells you which lines (or branches) your tests executed. It says nothing about whether they asserted anything useful. A test that calls a function and checks nothing at all still turns its lines green. That's why coverage is best used as a detector of untested code, not a score of test quality: a low number is a real warning; a high number proves very little.
- Statement (line) coverage — did this line run? Cheap to hit, easy to game.
- Branch coverage — did both sides of every
ifrun? Much harder to fake, because it forces you through the error paths and edge cases where real bugs hide.
The 80 / near-100 split
Instead of one suite-wide target, split your codebase into two tiers:
| Tier | Target | Metric | Examples |
|---|---|---|---|
| Everything | ~80% | statement coverage | handlers, UI glue, utilities |
| Critical paths | near 100% | branch coverage | auth, payments, data mutation |
The ~80% overall figure keeps the suite honest without forcing tests onto trivial code. The near-100% branch target goes only where a missed edge case costs real money or real data: authentication and authorization decisions, anything that moves money, and any code that mutates persistent state. Fowler's own ballpark for thoughtfully tested code is "upper 80s or 90s" — reached as a consequence of good testing, never mandated as a quota.
Enforce the split in CI, not in code review
Modern tools support tiered thresholds directly, so the policy is a config file rather than a reviewer's memory. Jest takes per-glob thresholds — global floor, per-path ceiling-pushers:
coverageThreshold: {
global: { statements: 80 },
'./src/auth/': { branches: 100 },
'./src/payments/': { branches: 100 },
}
Jest fails the run if any tier misses its own number; patterns are checked independently of the global floor.
Python (coverage.py): enable branch measurement with coverage run --branch (or branch = true under [run] in the config), and set fail_under in the [report] section — coverage exits with status 2 if the total lands below it. Run a second, stricter config scoped to your critical packages via include to enforce the near-100% tier.
Power-user moves
- Ratchet, don't leap. Set
fail_underto your current number, then raise it one point with each PR that improves it. A ratchet never lets coverage decay and never demands a big-bang test-writing sprint. - Read the uncovered lines, not the percentage. The most useful coverage output is the red-line report on your critical modules — each uncovered branch there is a named, specific risk you either test or consciously accept.
- Exclude the noise. Generated code, migrations, and
__repr__-style trivia belong in your exclude list. Every excluded trivial line makes the remaining number more meaningful. - Beware assertion-free tests. If a module has high coverage but bugs keep escaping, spot-check its tests for real assertions — coverage detects code your suite never runs, but as Brian Marick noted, it can't detect a suite that runs everything and checks nothing.
The takeaway
Coverage is a tool, not a trophy. Aim for roughly 80% statement coverage suite-wide, demand near-100% branch coverage on auth, payments, and data mutation, and wire both tiers into CI so the standard enforces itself. Your tests concentrate where a bug actually hurts — and you stop gaming a suite-wide number nobody should trust.
Resources
- Test Coverage — Martin Fowler
- Branch coverage measurement — coverage.py docs
- Configuration reference (
fail_under,branch) — coverage.py docs coverageThreshold— Jest configuration docs
Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.