100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Software Testing

Test Coverage Explained

Understand how line, branch, and path coverage are measured, why 100% coverage doesn't guarantee correctness, and how to use coverage reports wisely.

Unit TestingIntermediate10 min readJul 10, 2026
Analogies

What Test Coverage Actually Measures

Test coverage is a metric produced by instrumenting your code (via tools like Istanbul/nyc for JavaScript, coverage.py for Python, or JaCoCo for Java) that tracks which parts of the source were executed while the test suite ran, then reports that as a percentage. Line coverage is the simplest form — the percentage of executable source lines that ran at least once. Branch coverage is stricter: for every if/else, switch case, or ternary, it tracks whether both the true and false paths were actually exercised, not just that the line containing the condition ran. A function with a single if statement can show 100% line coverage while only ever testing the true branch, which is why branch coverage is generally considered the more meaningful signal.

🏏

Cricket analogy: Line coverage is like confirming every fielding position on the field had a player standing in it at some point during a match, while branch coverage is like confirming every fielder actually had to make both a catching attempt and a diving stop, the stricter, more meaningful check.

javascript
function processRefund(order) {
  if (order.amount > 1000) {
    // High-value branch — needs a test that hits this
    return requireManagerApproval(order);
  } else {
    // Standard branch
    return autoApprove(order);
  }
}

// A single test calling processRefund({ amount: 50 })
// achieves 100% LINE coverage of the if-statement line,
// but only 50% BRANCH coverage — the manager-approval
// path is never exercised.
test('auto-approves small refunds', () => {
  expect(processRefund({ amount: 50 })).toEqual({ approved: true });
});

// This second case is required for full branch coverage:
test('requires manager approval for large refunds', () => {
  expect(processRefund({ amount: 1500 })).toEqual({ pendingApproval: true });
});

Why 100% Coverage Doesn't Mean Bug-Free

Coverage tools only report whether a line or branch executed, not whether the test that executed it actually asserted anything meaningful about the result. A test that calls processRefund({amount: 1500}) but only checks that it 'didn't throw an exception' achieves the same 100% branch coverage as a test that thoroughly verifies the manager-approval object's exact shape, yet the first test would let a genuine logic bug — returning the wrong approval status — pass silently. Coverage also can't detect missing requirements entirely: if a business rule like 'refunds over $10,000 require two-manager approval' was never implemented and never specified, no coverage metric flags the gap, because there's no code path to measure in the first place.

🏏

Cricket analogy: A fielder standing at every position on the ground (100% coverage of positions) doesn't mean they actually caught every catchable ball hit to them — presence at the position isn't the same as making the right play, just as line execution isn't the same as correct verification.

Using Coverage as a Diagnostic Tool, Not a Target

The most productive use of a coverage report is spotting untested code, not chasing a percentage. A coverage report highlighting that the manager-approval branch and the refund-rejection branch are both never executed is a direct, actionable signal: write tests for those specific paths. Treating coverage as a hard gate ("CI fails below 90%") without judgment, however, incentivizes developers to write low-value tests just to nudge the number up — for instance, testing a trivial getter/setter or a framework-generated constructor purely to bump the percentage, while genuinely risky logic like the refund threshold calculation remains under-tested relative to its importance. Coverage should inform where to look, and the developer's judgment about business risk should decide where to actually invest testing effort.

🏏

Cricket analogy: A heat map showing which areas of the field a batsman rarely hits toward is a diagnostic tool for bowlers to exploit, not a target score itself, the same way a coverage report points at gaps rather than being the goal.

Mutation testing (via tools like Stryker for JavaScript or PIT for Java) goes a step further than coverage: it deliberately introduces small bugs ('mutants') into your code, like flipping a > to >=, and checks whether your test suite actually fails. A test suite with 100% line coverage that fails to catch most mutants reveals that the tests execute the code without meaningfully verifying it.

Beware of coverage-driven test theater: deleting assertions or replacing them with try/catch blocks that swallow exceptions just to keep a test 'green' while still executing (and thus 'covering') the code. This inflates the coverage percentage while providing zero actual protection against regressions, and is often worse than having no test at all because it creates false confidence.

  • Coverage tools instrument code to report which lines or branches executed during the test run.
  • Line coverage tracks executed lines; branch coverage tracks whether both true and false paths of each condition ran.
  • 100% coverage only proves code executed, not that the test verified the result was correct.
  • Coverage cannot detect entirely missing requirements, since there's no code path to measure for unimplemented logic.
  • Coverage is most useful as a diagnostic to find untested paths, not as a hard percentage target.
  • Hard coverage gates can incentivize low-value tests written just to nudge the number up.
  • Mutation testing goes further by verifying tests actually fail when the code is deliberately broken.

Practice what you learned

Was this page helpful?

Topics covered

#SoftwareTesting#TestingTDDStudyNotes#SoftwareEngineering#TestCoverageExplained#Test#Coverage#Explained#Actually#Testing#StudyNotes#SkillVeris