The Software Testing Course
    test coveragemetricsfundamentals

    Test Coverage: What the Number Does and Doesn't Tell You

    Part 12 of the Software Testing Course: what code coverage measures, why 100% doesn't mean 'tested', and better ways to think about how much testing is enough.

    Last updated: August 13, 2026
    by Manta AI Team4 min read

    "We're at 85% coverage" is one of the most quoted and least useful numbers in testing. It gets reported to management, gated on in CI, and argued about in retros — and it answers a much narrower question than people think. This part is about what it actually measures and what to look at instead.

    What code coverage measures

    Code coverage tools instrument your code, run your test suite, and report which parts executed during the run. If a line ran while a test was running, it's covered. That is the entire definition — execution, not verification.

    • Line / statement coverage — was this line (or statement) executed at all? Most tools report these together; a line with several statements is where they diverge.
    • Branch coverage — was each side of each if / switch / ternary taken? (Stricter and more useful than line coverage.)
    • Function coverage — was each function called?

    Stricter measures exist for high-assurance work — path coverage (every route through a function) and MC-DC (modified condition/decision coverage, mandated in avionics and similar) — but they're rarely tracked outside safety-critical software.

    A function can be 100% line-covered by a test that calls it and asserts nothing.

    What it doesn't tell you

    • Whether the test checked anything. it('renders', () => { render(<Thing />); }) with no assertion covers every line render touches. The coverage report can't distinguish that from a test with ten meaningful assertions.
    • Whether the right things are tested. 85% coverage that skips the payment flow is worse than 60% that covers it thoroughly. The number is an average; it hides which 15% is missing, and the missing 15% is rarely random.
    • Whether the behaviour is correct. Coverage can't see a missing requirement or a wrong spec. A fully-covered function that does the wrong thing reports as fully covered. That gap is validation, and no coverage tool touches it.
    • End-to-end reality. Unit-test coverage says nothing about whether the assembled application works. No unit test drives the real UI, so a codebase at 90% unit coverage can still have a sign-up button wired to nothing.
    • Integration and configuration paths. The bug is often in how modules are wired together or how config is loaded, not in a line inside a module — and line coverage of the modules looks fine.

    Why 100% is a trap

    Pushing coverage toward 100% changes team behaviour in bad ways:

    • Tests for trivial code. Getters, setters, constants, config objects — written purely to lift the number, catching nothing.
    • Over-mocking. To cover a hard-to-reach branch, developers mock enough of the surrounding system that the test now verifies "my code called these functions" rather than "my code produced the right result." See mock vs stub vs fake.
    • Contorted tests for defensive code. The // should never happen branch gets a test that fakes an impossible state, adding maintenance for a line that protects against nothing real.

    The last 10–15% of coverage is usually the most expensive to get and the least valuable. A team that treats 80% as "good, and we know what's in the other 20%" is in a better position than one that hit 100% by any means necessary.

    Better questions than "what's our coverage?"

    • Are the critical user journeys covered end to end? A yes/no per journey — sign-up, login, checkout, the core loop — is a far more honest signal than a percentage.
    • When a regression escaped to production, was that area "covered"? If the answer is repeatedly yes, your coverage is hollow — the tests execute the code but don't assert the things that break.
    • How much of the running app does anything exercise at all? Autonomous exploration gives a behavioural answer that unit coverage can't: the navigation map shows which flows and states were actually reached, which is closer to "is the product tested" than any line-count.
    • How much of the team's testing time goes to maintaining tests versus adding new coverage? If it's mostly maintenance, the suite's shape is wrong regardless of the number.

    How to use the number without being used by it

    Coverage is a useful floor and a useful diff. As a floor: "don't let a module drop below X%" catches whole files that shipped with no tests. As a diff: "this PR adds 200 lines and 0 covered lines" is a reasonable thing to flag in review. As a target to maximise, it drives the pathologies above.

    The takeaway

    Code coverage measures execution — not verification, not correctness, not whether you tested the right things, and not whether the assembled app works. Use it as a floor to catch untested files and as a signal on new code in review. Track covered journeys and escaped bugs as the real measures of "enough." Part 13 turns to the testing the pyramid tends to ignore entirely: non-functional testing.