Coverage Is Not Confidence
Code coverage measures exactly one thing: which lines were executed while the tests ran. It’s a real and useful signal — a line that never runs during testing has definitely never been verified, and that’s worth knowing. But it’s routinely read as something it isn’t. A high coverage number gets treated as a statement about how well-tested the code is, when all it actually reports is how much of the code was touched. Touched and checked are different things, and the whole trouble with coverage as a headline metric lives in that gap.
You can see the gap by writing a test that executes a function, asserts nothing, and passes. It contributes to coverage identically to a test that carefully verifies every output. The lines ran; the tool counts them; the number goes up. A more realistic version is subtler: a test that checks the happy-path return value but never looks at whether the error case does the right thing, even though the error branch executed as a side effect of setup. The branch is covered. Its behavior is unverified. The metric can’t tell the difference, because the metric was never measuring verification — it was measuring execution, and we quietly started hoping it meant more.
This matters because coverage is easy to game without ever intending to. When a number becomes a target — “we require 80%” — people write the tests that move the number, and the cheapest tests to write are the ones that execute code without meaningfully asserting on it. You end up with a suite that’s expensive to maintain, slow to run, and provides far less protection than its coverage percentage advertises. Worse, it provides false confidence: people refactor boldly and ship quickly because the tests are “good,” and the tests were good at running the code and bad at checking it. The number went green on the way to the incident.
What coverage genuinely tells you is where you’re blind, and that’s how it’s worth using — as a floor, read in the negative. Uncovered code is code no test exercises at all, and that’s a real gap worth closing. But covered code is not therefore safe; it’s merely code that some test happened to run, and whether that test would notice if the behavior broke is a separate question the coverage tool cannot answer. The useful question was never “what percentage is covered” but “if this logic were wrong, would a test fail?” — and that one has to be answered by looking at the assertions, which no percentage summarizes.
The deeper point is that a test’s value is entirely in what it would catch, not in what it runs. A test that can’t fail for any realistic bug is worthless no matter how much it covers, and a handful of tests that pin down the behavior that actually matters are worth more than a suite that drives every line while checking almost nothing. Coverage is a cheap proxy that’s been promoted, in a lot of places, into a goal — and when a proxy becomes a goal, people optimize the proxy. Track it if you like, as a way to find the untested corners. Just don’t confuse the map of what ran with the territory of what’s actually known to work.