What Is Code Coverage? (And Why 100% Isn't the Goal)
Code coverage is a metric that measures the percentage of your code executed by your automated tests. Here is what the number actually means, the types of coverage, and why chasing 100% is the wrong target.
Definition
Code coverage is a metric that measures the percentage of your source code executed by your automated tests. A tool instruments the code and records which lines, branches, and functions ran during the test suite. It reports what your tests touch — not whether those tests actually verified that the code behaved correctly.
Mechanically, it works like this. A coverage tool instruments your code — it inserts invisible bookkeeping so that every time a line, a branch, or a function executes, a counter ticks. You run your test suite, the counters accumulate, and at the end the tool divides executed code by total code and hands you a percentage. When a report says 82% line coverage, it means 82% of your executable lines ran at least once while the tests were running. The other 18% never ran at all, which is a genuinely useful thing to know.
The ISTQB glossary puts it more formally: coverage is 'the degree to which specified coverage items are exercised by a test suite, expressed as a percentage.' The phrase to hold onto is exercised. Coverage tells you a piece of code was reached during testing. It does not — and cannot — tell you whether the test that reached it checked that the code did the right thing. That gap is the entire reason this article exists, and we will return to it.
The types of coverage
'Coverage' is not one number — it is a family of metrics that measure different things, from cheap-and-shallow to expensive-and-thorough. The four you will actually meet, weakest to strongest:
- Line / statement coverage — which lines (or statements) executed at least once. The most quoted number and the easiest to inflate. It answers only 'did this line ever run?'
- Function / method coverage — which functions were called at least once. Coarse, but a fast smoke check for whole modules that no test ever touches.
- Branch / decision coverage — for every
if,else, ternary, and loop condition, were both the true and false outcomes taken? Meaningfully stronger than line coverage. - Condition / path coverage — did every boolean sub-condition take both values (condition), or did every distinct route through the function execute (path)? The strongest, and the rarest.
Condition and path coverage go further still. A single decision like if (a && b) has multiple boolean sub-conditions, and a function with several branches has many possible routes through it. Exercising all of them is genuinely thorough — but the number of paths grows combinatorially with each added branch, which is why full path coverage is impractical for most codebases and is generally reserved for safety-critical software (avionics, medical devices) where a missed path can cost lives. For everyday product work, branch coverage is the sweet spot: much stronger than line coverage, without the combinatorial explosion.
How teams use it — and how it goes wrong
In practice, coverage shows up in two places. First, as a CI gate: a threshold that fails the build if coverage drops below, say, 75%, or if a pull request lowers the project's overall figure. Second, as a diagnostic: you open the coverage report, look at the red (un-executed) lines, and discover that your payment-retry logic or your error-handling branch has no tests at all. That second use is where coverage earns its keep — it is a map of what your suite ignores.
The trouble starts when the number becomes the goal. Here is the crucial, honest point that gives this article its title: coverage measures what is executed, not what is verified. Those are not the same thing. A test can call a function, run every one of its lines, and then assert nothing whatsoever about the result. The coverage tool sees the code run and dutifully counts it. The dashboard turns green. And you have learned precisely nothing about whether the code is correct.
Coverage measures what your tests execute, never what they verify. A suite can hit 100% coverage while asserting nothing — running every line, checking no behavior. A green coverage number is evidence your tests touched the code, not evidence the code is correct or the tests are any good.
Martin Fowler has a name for the failure mode: assertion-free testing — tests that exercise code without any assertions checking that it did the right thing. They exist precisely because a coverage target creates the incentive to write them. If your bonus, your CI, or your team's pride hinges on hitting 100%, the fastest route is not better tests; it is more tests that touch code without checking it. Coverage goes up, real defect-catching power stays flat, and everyone feels safer while being no safer at all.
What good use of coverage looks like
None of this means coverage is useless — it means you should use it as a flashlight, not a finish line. Fowler's own guidance in Test Coverage is blunt: coverage is a useful tool for finding untested parts of a codebase, but it is 'of little use as a numeric statement of how good your tests are.' The productive move is to invert the question. Instead of 'how do we get from 78% to 100%?', ask 'which of the paths my tests don't touch actually matter, and are any of them critical?'
Concretely, good practice looks like this: run branch coverage, not just line coverage, so the number means more. Read the report to hunt for untested critical paths — auth, payments, data integrity, error handling — and write assertion-rich tests for those specifically. Set a pragmatic floor (many teams land around 70–80%) that is low enough to be met with genuinely useful tests rather than gamed with filler. And treat the floor as a smoke alarm for regressions, not a badge. A well-judged 80% with strong assertions beats a gamed 100% every time.
It also pairs well with other signals. Test-driven development tends to produce high coverage as a byproduct — you write the test first, so the code that exists is code a test drove into being, and it comes with real assertions attached. That is coverage earned honestly, not chased. And knowing which layer to test — unit, integration, or end-to-end — matters far more than the aggregate percentage, because a unit-tested function with a broken integration is still a broken feature at 100% unit coverage.
This connects to a broader trap worth naming: coverage is easy to turn into a vanity metric — a number that looks reassuring on a dashboard while telling leadership little about whether the product actually works. The metrics that matter measure escaped defects, mean time to resolution, and user-facing reliability. Coverage informs those; it does not replace them. A team optimizing for the coverage bar instead of for shipped correctness is optimizing for the wrong thing.
| Feature | Aspect | Coverage as a diagnostic | Coverage as a 100% target |
|---|---|---|---|
| What it optimizes for | — | Finding untested critical paths | Hitting a number |
| Incentive it creates | — | Write meaningful, assertion-rich tests | Write assertion-free tests that game the metric |
| Preferred metric type | — | Branch / decision coverage | Line coverage (easiest to inflate) |
| Typical healthy signal | — | ~70–80% with strong assertions | 100% with unknown assertion quality |
| Tells you the tests are good | — | — | — |
And here is the limit that no coverage number can escape, which is where a bug tracker enters the story. Coverage can only report on code your tests run. It is structurally blind to the bugs your tests never imagined — the input shape you didn't anticipate, the race condition on a slow network, the interaction between two features nobody tested together. Those defects sail past a 100% coverage gate untouched, because there was never a test to cover them in the first place. They reach real users, and then they need reproduction, not a coverage report.
A 100% coverage gate is blind to the defects that reach real users — the ones no test covered. BugMojo captures those in the wild: an rrweb session replay, the console, and the network state behind the failure, handed to Claude Code or Cursor over MCP so your agent can reproduce and fix what your suite missed.
Install the extensionFrequently asked questions
Frequently asked questions
Sources
- Test Coverage — why coverage is a useful diagnostic but a poor target (Martin Fowler) — martinfowler.com (2026)
- Code coverage — line, branch, function and condition coverage defined — Wikipedia (2026)
- Assertion-Free Testing — tests that execute code without checking behavior — martinfowler.com (2026)
- Coverage — 'The degree to which specified coverage items are exercised by a test suite' (ISTQB Glossary) — ISTQB (2026)
Get bug-tracking insights, weekly.
Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.

