Also called Coverage gap, Untested code, Untested hotspot, Patch coverage gap
In depth Untested hotspots: what to test first: the ranking, the thresholds and what to do about each gap.
How a test coverage gap is measured
A gap is a judgment built from coverage plus context, not a single metric.
- Line coverage: executed lines divided by executable lines in a file.
- Branch coverage: branches taken divided by branches present.
- Patch coverage: of the lines a change added or modified, the share that ran under tests.
A worked example. A pull request changes 40 executable lines, and tests run 26 of them:
patch coverage = 26 / 40 = 65%
The repository's total coverage may still be 85%, because the 40 changed lines are a small part of it. Weighting by risk is what makes a gap meaningful: an untested 20-line script that nobody imports is not the same as an untested payment router edited weekly. Useful context signals are churn, the number of dependents (the blast radius), and a method-level formula such as the CRAP score, which rates a single method and is not the same thing as the gap itself.
Why test coverage gaps matter
Bugs concentrate where code changes often and nobody has pinned its behavior. A gap in rarely touched code costs little. A gap in a hot file means each edit is unverified, and reviewers cannot see what the diff does not cover.
Chasing a global percentage is the usual failure mode. It rewards easy tests on safe code and leaves the risky file at zero. Patch coverage on each change, plus a ranked list of hot and depended-on files, points effort where it prevents regressions.
How to measure test coverage gaps yourself
Produce a report with your runner, for example:
pytest --cov --cov-report=lcov:coverage.lcov # Python (pytest-cov)
lcov --list coverage.lcov # per-file summaryOther runners have equivalents: coverage.py, Istanbul/nyc or c8, JaCoCo, and go test -coverprofile. Rank files by edits with git log --since="90 days ago" --name-only --pretty=format: | grep -v '^$' | sort | uniq -c | sort -rn | head -20, then compare against the coverage list. For patch coverage in CI, diff-cover reads a coverage report and a git diff and reports the changed-line percentage; it is free and a good pick if you only need that gate. The weak point of the manual route is counting dependents, which needs a dependency graph.
How repowise measures test coverage gaps
See test coverage. Everything below is deterministic, with no LLM calls.
- Ingest:
repowise coverage add <report>reads 7 formats, detected from content: lcov, Cobertura, Clover, repowise-json, Go coverprofile, JaCoCo and Apex.repowise coverage statussummarizes what was read. coverage.py and lcov per-test contexts are also supported. - Findings:
untested_hotspotfires when a file is a git hotspot, has at least 4 dependents and line coverage below 40%. With no report, it fires only when no paired test file and no test reaching it through the call graph is found. Files with partial coverage fall under the separatecoverage_gapfinding. See code health. - Measured versus inferred: with no report, test reach comes from the call graph and is labelled inferred, never given as a percentage.
- CI gates:
repowise coverage check origin/main...HEAD --report coverage/lcov.info --fail-under 80gates patch coverage. Related options are--fail-under-risky,--fail-under-branches,--base-reportwith--max-drop, and--min-coverable-lines.repowise coverage suggest-gatesproposes settings and writes nothing. - Agents:
get_riskwithinclude=["tests"]returns recommended tests, labelled measured or inferred, andget_change_riskincludes patch coverage and the tests that reach a diff.
The guide to untested hotspots covers ranking and what to do next.