HT
How Things Work
System Online

Code Coverage & Quality Gates

What coverage actually measures — line vs branch coverage, why 100% doesn't mean correct, and how to use thresholds as a guardrail instead of a vanity metric.

How It Works

Code coverage measures which parts of your code your tests execute — lines, statements, branches, functions. It's a useful map of untested areas, with branch coverage being far more meaningful than line coverage. But coverage shows only that code ran, not that you asserted the right things, so it's a guardrail to prevent regression, not a target that proves quality.

1
What Coverage Measures

Code coverage tools run your tests while recording which parts of the code executed. They report metrics like line coverage (lines run), statement, function, and branch coverage (which decision outcomes — both the if and the else — ran). It's an objective map of what your tests touched.

2
Line vs Branch Coverage

Line coverage is the headline number but the weakest: a test can run every line yet only ever take one side of each if. Branch (decision) coverage is stricter — it requires every conditional outcome to be exercised. 100% line with 60% branch means half your decision logic is untested.

3
Coverage Is Necessary, Not Sufficient

Coverage tells you what code ran, not whether your assertions were meaningful. You can hit 100% coverage with tests that assert nothing useful — executing code without verifying its behavior. High coverage on bad tests gives false confidence; it's a floor, not proof of quality.

4
Use It as a Guardrail

The healthy use: set a reasonable threshold (often branch coverage) as a CI guardrail to prevent coverage from regressing, focus coverage on important logic, and exclude generated or trivial code. Treat low coverage as a signal to investigate — and never treat a high number as a goal in itself (Goodhart's Law).

Key Concepts

📏Line Coverage

Percentage of code lines executed by tests. The most common but weakest metric.

🌿Branch Coverage

Percentage of decision outcomes (both if and else) exercised. Stricter and more meaningful.

📊Statement / Function

Other granularities coverage tools report — statements run, functions called.

⚠️Coverage ≠ Correctness

Coverage shows code ran, not that assertions were meaningful — you can have 100% with useless tests.

🚧Coverage Threshold

A CI gate (e.g. branches ≥ 80%) that prevents coverage from regressing over time.

🎯Goodhart's Law

When a measure becomes a target it stops being a good measure — gaming coverage produces hollow tests.

Coverage's limits & thresholds
tsx
1// This single test gives 100% LINE coverage of a 1-line function...
2function isAdult(age) { return age >= 18; }
3test("adult", () => { expect(isAdult(20)).toBe(true); }); // line: 100% ✅
4
5// ...but never checks the boundary or the false branch. The bug below
6// (>= vs >) would pass with 100% line coverage:
7function isAdult(age) { return age > 18; } // 🐛 18 now returns false
8// → you need branch + boundary tests, not just a green coverage number.
9
10// Coverage as a guardrail (don't let it DROP), not a vanity target:
11// jest --coverage --coverageThreshold='{"global":{"branches":80}}'
12// Track BRANCH coverage (stricter than line) and exclude generated code.
💡
Why This Matters

Coverage is ubiquitous in CI and a classic interview 'gotcha' (does 100% coverage mean bug-free? — no). Understanding the difference between line and branch coverage, and why a high number can hide weak tests, lets you use coverage wisely — as a signal for gaps and a regression guardrail — rather than chasing a misleading score.

Common Pitfalls

Treating a coverage % as the goal — it invites tests that execute code without meaningful assertions.
Relying on line coverage alone, which misses untaken branches and boundary cases.
Assuming 100% coverage means bug-free — it can't catch missing tests or wrong assertions.
Setting thresholds so high they encourage gaming (asserting nothing) rather than real testing.
Counting generated/trivial code in coverage, distorting the number in both directions.
Real-World Use Cases

1The 95% Coverage That Caught Nothing

Scenario

A team proudly maintained 95% line coverage, yet bugs kept reaching production. Reviewing the tests revealed many executed code paths but barely asserted anything — they ran the code and checked it 'didn't throw'.

Problem

Coverage was treated as the goal, so tests were written to touch lines, not to verify behavior. High line coverage masked weak assertions and ignored branch/edge cases entirely — a textbook false sense of security.

Solution

Shift the metric to branch coverage as a guardrail, and review tests for meaningful assertions (correct outputs, error cases, boundaries) rather than execution. Add tests for the untested branches and edge cases the number had hidden. Coverage becomes a signal, not a scoreboard.

💡

Takeaway: A high coverage number doesn't mean good tests. Measure branch coverage, insist on meaningful assertions, and treat coverage as a guardrail that flags gaps — never as proof of quality.

2Coverage Silently Eroding on a Big Codebase

Scenario

On a large, fast-moving repo, new code was often shipped with few or no tests. There was no signal until coverage had quietly slid from 80% to 55% and the suite no longer protected critical paths.

Problem

Without a gate, coverage decays as features outpace tests. The erosion is invisible day-to-day and only noticed once the safety net has already thinned dramatically.

Solution

Add a CI coverage threshold (e.g. fail the build if branch coverage drops below the current level or a set floor), reported per-PR so authors see the impact of their change. New code must come with tests to keep the build green, halting the slow decline.

💡

Takeaway: Use coverage thresholds in CI as a ratchet that prevents regression, surfaced per-PR. The point isn't a perfect number — it's stopping the safety net from silently unraveling as the codebase grows.

Join the discussion

Comments

?

Loading comments...

Cookie preferences

We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.