Glossary · Change and test risk

What is a test coverage gap?

A test coverage gap is code that changes often or that many other files depend on, yet no test exercises. Total coverage can hide it: a repository at 80% can leave its most important file untested. Gaps are found by combining coverage reports with change frequency, dependents and what a diff touched.

Last reviewed: November 2026

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 summary

Other 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 status summarizes what was read. coverage.py and lcov per-test contexts are also supported.
  • Findings: untested_hotspot fires 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 separate coverage_gap finding. 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 80 gates patch coverage. Related options are --fail-under-risky, --fail-under-branches, --base-report with --max-drop, and --min-coverable-lines. repowise coverage suggest-gates proposes settings and writes nothing.
  • Agents: get_risk with include=["tests"] returns recommended tests, labelled measured or inferred, and get_change_risk includes patch coverage and the tests that reach a diff.

The guide to untested hotspots covers ranking and what to do next.

FREQUENTLY ASKED

Test coverage gap: questions

What is the difference between patch coverage and total coverage?

Total coverage is the share of all executable lines that tests ran. Patch coverage is the share of lines changed in a pull request that tests ran. Patch coverage asks whether this change is tested and is harder to game.

How do you find coverage gaps?

Generate a coverage report, then rank files by how often they change and how many files depend on them. Files that are hot, widely used and poorly covered are the gaps that cost the most.

Does 100% coverage mean no coverage gaps?

No. Coverage shows which lines ran, not whether tests assert anything useful. Branch coverage closes part of the gap, since a line can run while one of its branches never does.

How does repowise find gaps without a coverage report?

It infers test reach from the call graph, finding test files whose calls reach a source file, and labels the result as inferred. It never turns an inferred answer into a coverage percentage.

See it on your own repository.

Index a repository free and read the result for your own code and history. Rolling it out to a team? We will walk you through it.