Ch. 22 · Software Testing

Code Coverage and Its Limits

Read line and branch coverage as a signal, not a goal, and avoid the tests that chase the number without checking behavior.

~2 min readintermediateupdated Oct 5, 2026

Coverage measures which lines or branches your tests execute. It is a useful signal for finding untested code, but it says nothing about whether the tests assert the right thing, so treating it as a target produces tests that run code without checking behavior.

Before you start

You should be comfortable with tests and a coverage tool. This article covers what coverage does and does not mean.

Step-by-step walkthrough

Step 1: Treat line coverage as a map

Line coverage shows which lines ran, which helps spot whole modules with no tests. It does not show whether the result was correct, so a test that calls a function and asserts nothing can still raise coverage.

Step 2: Prefer branch coverage for conditionals

Branch coverage tracks whether both sides of each condition ran, which is more meaningful than line coverage for logic with if and ternaries. Adding a branch target encourages tests for the else path instead of only the happy path.

Step 3: Do not chase a number

A coverage threshold can be a floor that catches regressions, but pursuing 100% leads to tests that exercise code without assertion or that test trivial code. Coverage of critical logic is what matters; the percentage is a proxy, not the goal.

Worked scenario

The test raises coverage without asserting behavior.

test('does not crash', () => {
  processOrder({ id: 'o-1', total: 10 }); // no assertion: coverage only
});
test('computes total', () => {
  expect(processOrder({ id: 'o-1', total: 10 }).total).toBe(10);
});
JavaScript

Walk through the example

The first test executes processOrder and adds coverage but verifies nothing, so a regression that changes the result still passes. The second asserts the outcome and would fail if the computation broke. Both may count the same lines, but only one is a real test.

Common mistake

Chasing a coverage percentage, which incentivizes assertion-free tests and coverage of getters. Another is excluding hard-to-test code from the report to hit a target, which hides the real gap.

Verify the behavior

Introduce a bug in a covered line and confirm a real test fails; if it does not, the coverage was hollow. Compare branch coverage before and after adding a test for the else path. Review covered code for tests with no assertions.

Interview exercise

Can you have high coverage and still have poor tests?

Answer and reasoning

Yes. Coverage only records that code ran, not that the result was checked. A suite of tests that call functions without asserting their output can reach high coverage while catching almost nothing. The quality comes from meaningful assertions on behavior, which coverage cannot measure.

Continue learning

Compare assertions in Behavior assertions and confidence in Mutation testing. Read the Martin Fowler on test coverage and try the Testing interview questions.

More in Software Testing

read ✓Software Testing · hard

Testing Time with an Injected Clock

Inject a clock instead of calling the system time, freeze it in tests, and remove the sleeps that make tests flaky.

~2 min readread →
read ✓Software Testing · hard

Scoping End-to-End Tests

Reserve end-to-end tests for critical user journeys, use stable selectors, and avoid duplicating coverage the lower tests already have.

~2 min readread →
read ✓Software Testing · mid

Testing Error Paths

Test failures and edge inputs deliberately, assert the error type and message, and stop the happy path from hiding bugs.

~2 min readread →
esc