Code coverage
Code coverage measures which parts of a codebase were executed while a test suite ran, expressed as a percentage. It answers "what did the tests never touch", which is useful. It does not answer "was the behaviour actually verified", which is the question people assume it answers.
The four metrics
| Metric | Counts | Catches |
|---|---|---|
| Line | Executed lines | Whole files nobody tests |
| Statement | Executed statements (multiple per line) | Chained expressions |
| Branch | Executed outcomes of each decision point | The untested else |
| Function | Called functions | Dead or forgotten entry points |
Branch is the informative one. An if with no else can show 100% line
coverage while the false path has never run once. A suite at 85% line
and 60% branch is a suite that exercises happy paths.
The formats
- lcov (
lcov.info) — the GNU tracefile format, emitted by Istanbul/nyc,cargo-llvm-cov, gcov and others (share an lcov report). - Cobertura XML — the format most CI systems and code hosts ingest for PR annotations (share a Cobertura report).
- JaCoCo — XML plus its own HTML tree on the JVM (share a JaCoCo report).
coverage.py— XML, JSON andhtmlcov/for Python (share a coverage report).- SimpleCov for Ruby,
dotnet-coverage/ Coverlet for .NET.
Every one of these has a machine format for the gate and an HTML tree for the person who has to act on it.
Why the HTML tree is the part that matters
A percentage in a PR comment tells a reviewer that coverage dropped. It does not tell them which new branch went untested, and that is the only actionable fact in the report.
The HTML output is a file tree you click into, with each source line coloured by whether it ran and each partially-covered branch flagged. That drill-down is the report. Screenshotting the summary number discards it, and so does a coverage badge.
The drill-down is also why the report is multi-file: the tree is one
generated page per source file, cross-linked. Copying index.html out
of htmlcov/ gives a page whose every link 404s
(coverage report links broken).
Coverage's blind spot
Coverage counts execution, not assertion. A test that calls a function and asserts nothing gives it full coverage. Mutation testing is the direct answer: it changes the code and checks whether any test notices. Suites at 90% coverage routinely score far lower on mutation score, and the gap is the honest measure of the suite.
Publishing the tree
Publish the whole coverage directory rather than the summary. Reviewers open the file, see the uncovered branch, select it, and comment on that line — instead of pasting a percentage into chat and arguing about thresholds (comment on HTML).
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history.