The Hidden Cost of 100% Test Coverage

High coverage metrics feel safe. But chasing 100% can give you a false sense of security while hiding the tests that actually matter.

There’s a number that makes engineering managers feel good and makes experienced quality engineers nervous: 100% test coverage.

2026 update: This problem has gotten worse with AI-generated tests. I wrote about it in Auto Mode and Author Agents — the coverage trap is now automated.

It looks great on a dashboard. It sounds responsible in a sprint review. And it can absolutely hide a deeply broken testing strategy.

Let me explain.

Coverage Measures Execution, Not Value

When a line of code is “covered,” it means some test caused that line to execute. That’s it. It says nothing about:

  • Whether the test asserts anything meaningful
  • Whether the behavior under test is the right behavior
  • Whether the test would actually catch a regression

I’ve seen codebases with 95%+ coverage where the tests were essentially expect(true).toBe(true) in a trench coat — green, meaningless, and comforting to exactly the wrong people.

Branch Coverage Over Line Coverage

Most teams default to line coverage. But line coverage lies by omission — it tells you a line executed, not which path through it was taken.

Consider:

if (user.IsActive && user.HasPermission("admin"))
    GrantAccess();

Line coverage reports 100% if any combination of conditions causes GrantAccess() to run. But you haven’t tested the case where IsActive is true and HasPermission is false. Or where both are false.

Branch coverage forces you to exercise every decision point — every if, every else, every ternary, every switch case. It catches the off-ramps that line coverage ignores. If you’re going to measure one metric, measure branches.

Exclude What Doesn’t Matter

Not all code should be measured. Auto-generated code, DTOs, configuration bootstrapping, third-party wrappers — these inflate your denominator and dilute the signal. Covering a Program.cs that wires up dependency injection tells you nothing about your application’s behaviour.

Use exclusion attributes ([ExcludeFromCodeCoverage] in .NET, /* istanbul ignore */ in JS/TS) deliberately:

  • Generated code (protobuf, OpenAPI clients, database migrations)
  • Pure data models with no logic
  • Framework boilerplate
  • Adapter/wrapper code that delegates to a tested dependency

Excluding these sharpens your coverage metric to reflect code that has decisions worth testing. Your 72% suddenly means something.

The Coverage Trap

The trap works like this:

  1. Leadership sets a coverage threshold (say, 80%)
  2. Engineers hit it using the path of least resistance — shallow tests, trivial assertions, mocking everything
  3. The number stays green, confidence goes up
  4. A real bug slips through because the important behavior was never meaningfully tested
  5. Everyone is surprised

The coverage metric didn’t fail. The thinking behind it did.

What Actually Predicts Quality

If you want tests that catch real bugs, think about:

Risk-weighted coverage — not all code is equally risky. A payment processing function matters more than a utility that formats a date string. Allocate your testing effort accordingly.

Mutation testing — tools like Stryker deliberately introduce bugs into your code and check whether your tests catch them. It’s a more honest signal than line coverage.

Behaviour coverage — are you testing the scenarios your users actually encounter, including edge cases, error states, and unhappy paths?

Test failure rate — a test suite that never fails isn’t protecting you. It’s either watching code that never changes, or it’s not asserting anything that could fail.

The Right Way to Use Coverage

Coverage metrics aren’t useless. They’re useful for:

  • Finding untested code paths — high coverage means you’ve at least run most of your code under test conditions
  • Preventing regressions in critical paths — when something matters, make sure tests run through it
  • Team agreements — a coverage floor can prevent tests from being skipped entirely
  • Enforcing branch coverage over line coverage as the default metric

But as a goal, coverage is a proxy for a proxy. The goal is confidence that your software works correctly. Coverage is one weak signal among many.

Stop optimizing for the number. Start asking: if this code breaks, will my tests tell me?

That question is harder to answer. It’s also the only one that matters.


AI agents will generate 200 trivial tests if you let them. Author agents are one structural answer — quality gates that catch volume-over-value before it reaches your PR.