Test Coverage Gap Analysis Workflow
Map coverage against change frequency and incident history, target the high-risk low-coverage quadrant first, and measure by gap closure.
Objective
Direct test-writing effort where risk lives: files that change often, carry incidents, and lack coverage, rather than chasing a global percentage.
Environment
- Line or branch coverage reporting in CI
- Git history available for change-frequency analysis
- Incident or bug tracker with affected-file mapping
Steps
1. Join coverage with change frequency
Produce a scatter of every module: coverage percent against commits per month, identifying the high-churn low-coverage quadrant.
Expected state: A ranked list exists naming the files that change most while tested least.
2. Overlay incident history
Mark files that produced production incidents in the last two quarters, elevating them to the top of the gap queue.
Expected state: Incident-touched files with low coverage lead the priority list.
3. Classify the gaps by type
For top files, classify missing coverage: happy path, error handling, boundary values, or integration seams, since each needs different test styles.
Expected state: Each gap entry carries its type and the test approach planned.
4. Write tests gap-first, not percent-first
Close the top gaps with tests targeting the classified type, accepting that the global percentage moves little while risk drops much.
Expected state: Gap entries close with tests that fail against the old code when bugs are injected.
5. Institute change-triggered coverage gates
For files in the quadrant, require that new PRs either maintain or raise their file-level coverage, preventing re-entry into the gap.
Expected state: A PR touching a high-risk file with falling file coverage fails the check.
Expected outcome
Coverage effort concentrated on the churn-plus-incident-plus-untested quadrant, with per-file gates keeping gaps from reopening.
Verification
The next incident in a formerly gapped file is caught by a test added in this program; the quadrant population shrinks quarter over quarter.
A global percentage optimizes the wrong thing
Raising total coverage from 71 to 74 percent is easy: write tests for stable utility files. The risk lives in files that change weekly, broke production twice, and sit at 34 percent. Quadrant analysis points effort there.
quadrant (coverage x churn/month):
low churn high churn
high cover 61% of code 22% (fine)
low cover 14% (later) 3% (FIRST)
top of queue (incident-marked):
checkout/promo.ts 34% 9 commits/mo 2 incidents
api/webhooks.ts 21% 7 commits/mo 1 incident
(utils/string.ts 88% 0 commits/mo -- skip)
gap types: promo.ts -> boundary + error paths
12 tests added; global % moved 71.2 -> 71.9 (risk: down)
Incident history beats any heuristic
Churn predicts future change, but an incident proves past damage: that file already hurt the business with insufficient tests. Files with both markers and low coverage are not a theoretical risk; they are a documented one awaiting its next occurrence.
File-level gates stop the revolving door
Global coverage cannot detect a risky file degrading while some utility file’s tests offset it numerically. A per-file gate on the flagged quadrant keeps each gap closed once paid for, which is the only durable form of coverage progress.