Testing & QA

Test coverage — what percentage-covered actually tells you (and doesn't)

Coverage measures whether a line ran during the test suite — nothing about whether anything meaningful was actually checked when it did, which is a much weaker guarantee than the number suggests and a real target for gaming.

Intermediate

3 min read

What "100% covered" actually measures

function divide(a, b) {
  return a / b;
}
 
test("divides two numbers", () => {
  divide(10, 2); // this LINE runs — coverage tools mark divide() 100% covered
  // there is no expect() at all — nothing was actually verified
});

A coverage tool instruments the code and tracks which lines executed while the test suite ran — it has no concept of whether the result was checked against anything. This test genuinely gives divide 100% line coverage while asserting literally nothing, including nothing about the (very real, very missing) division-by-zero and non-numeric-input behavior. Coverage answers "did this line run," never "was this line's behavior actually verified" — a meaningfully different, weaker question.

Coverage as a target, not a floor, invites gaming it

// A team under pressure to "hit 90% coverage" can get there without
// improving quality at all — by writing tests that execute code paths
// without checking anything meaningful, exactly like the divide() example.
// The METRIC goes up. The actual protection against regressions doesn't.

This is Goodhart's Law showing up in a very concrete, common way: once "coverage percentage" becomes the thing being measured and rewarded, it's possible to optimize the number directly — adding assertion-free tests, or testing only the easiest, most trivial branches — without a corresponding improvement in the thing coverage was originally meant to signal (real protection against regressions). This is why coverage is much more useful as a floor that flags genuinely untested code for a human to look at than as a target teams are pressured to hit, which invites exactly this kind of gaming.

What coverage IS genuinely good for: finding code nobody thought to test

function calculateShipping(order) {
  if (order.weight > 50) return computeFreightRate(order); // 0% covered —
  if (order.isInternational) return computeCustomsRate(order); // nobody wrote
  return computeStandardRate(order);                            // a test for
}                                                                // these two branches at all

A coverage report pointing at computeFreightRate and computeCustomsRate as never having executed during any test run is a genuinely useful, honest signal — not "these branches are definitely buggy," but "nobody has verified these branches at all," which is exactly the kind of gap that's easy to miss just by reading code casually. This is coverage's real, legitimate use: a map of what's been exercised at all, pointing a human at what to look at next — not a score to optimize directly.

There's no universally "right" coverage percentage

Chasing 100% coverage on a codebase with a lot of simple, low-risk glue code (thin wrappers, generated boilerplate) usually means writing a lot of low-value tests just to move the number, for logic that was never actually at meaningful risk of a subtle bug. A lower percentage that's concentrated on genuinely complex, high-risk logic (billing calculations, auth checks, anything with real branching) is often a far more valuable test suite than a higher percentage spread thin. Coverage percentage alone, without looking at what's covered, doesn't distinguish between these two suites at all.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What does a code coverage percentage actually measure?

2. Why does treating coverage as a target (not a floor) invite gaming it?

3. What is coverage genuinely useful for?