Coverage-Driven Testing (Coverage as a Target)
Coverage-driven testing chases a coverage percentage as the goal, yielding tests that execute code without asserting behavior — high numbers, low confidence. Treat coverage as a diagnostic, focus on behavior, use mutation testing, and prioritize by risk.
Coverage-driven testing makes a code-coverage number the objective itself — "reach 90%" — rather than treating coverage as one signal about a test suite's thoroughness. When a metric becomes a target, it stops being a good measure: teams write tests that execute lines to satisfy the gauge without verifying anything.
Why It Happens
Coverage is easy to measure and put on a dashboard, so it becomes a convenient management KPI. A mandate ("no merge below X% coverage") creates pressure to hit the number however possible. Writing tests that call code without asserting is far cheaper than writing tests that genuinely verify behavior, so under pressure that's what appears. Goodhart's law does the rest.
Why It Hurts
High coverage with weak assertions gives false confidence: the build is green and the dashboard looks great, but the tests would not catch real regressions — they merely execute code. Effort is spent writing and maintaining low-value tests (testing trivial getters, calling methods and ignoring results) instead of testing risky logic. The metric is gamed, so it no longer tells anyone anything. Worse, it can crowd out meaningful testing while creating the illusion the system is well tested.
Warning Signs
- Tests invoke code but assert little or nothing.
- A hard coverage threshold gates merges.
- Trivial accessors are tested while complex logic is thin.
- Coverage is high but bugs still ship regularly.
Better Alternatives
Treat coverage as a diagnostic, not a target — use it to find untested areas, not to grade the team. Focus on behavior: write tests that assert meaningful outcomes for the cases that matter. Use mutation testing to measure whether tests actually catch injected faults, which exposes assertion-free coverage. Apply risk-based testing to concentrate effort on complex, high-impact, change-prone code rather than spreading thinly to hit a percentage.
How to Refactor Out of It
Stop using a single coverage percentage as a merge gate or a performance metric. Run mutation testing on critical modules to reveal where coverage is hollow, then strengthen assertions there. Redirect testing effort by risk: prioritize the code most likely to fail and most costly if it does. Keep coverage visible as a hint about gaps, paired with qualitative review of test value. The goal is confidence that tests catch defects — a number can support that goal but must never replace it.