Coverage & Reporting
Coverage measures which lines your tests execute. It reveals untested branches, not test quality - use it to find gaps, not as a quality score alone.
Search across all documentation pages
Coverage measures which lines your tests execute. It reveals untested branches, not test quality - use it to find gaps, not as a quality score alone.
uv add --group dev pytest-cov
uv run pytest --cov=src --cov-report=term-missingWhen to reach for this:
[tool.pytest.ini_options]
addopts = "--cov=src --cov-report=term-missing --cov-fail-under=80"
[tool.coverage.run]
source = ["src"]
branch = true
omit = ["*/tests/*", "*/__main__.py"]
[tool.coverage.report]
show_missing = true
skip_covered = falseuv run pytest
# Name Stmts Miss Branch BrPart Cover
# src/myapp/billing.py 42 3 12 2 91%uv run pytest --cov=src --cov-report=html
open htmlcov/index.htmlWhat this demonstrates:
--cov=src measures the installed package, not testsbranch = true tracks if/else paths--cov-fail-under=80 blocks CI below threshold| Column | Meaning |
|---|---|
| Stmts | Executable statements |
| Miss | Statements never run |
| Branch | Conditional branches |
| BrPart | Partially covered branches |
tests/ - inflated numbers. Fix: --cov=src only.branch = true in coverage config.term-missing output.omit migrations and generated files.| Alternative | Use When | Don't Use When |
|---|---|---|
| mutation testing (mutmut) | Test quality audit | Quick gap finding |
| diff-cover | PR-scoped coverage | Full project audit |
| No coverage tool | Prototype | Production services |
80% is a common floor. Critical modules (billing, auth) should be higher.
No. It only shows executed lines. Pair with review and mutation testing.
# pragma: no cover on defensively unreachable code.
--cov-report=term-missing with branch = true.
--cov-fail-under=80 in pytest addopts.
pytest --cov=src/myapp/billing.
Reports coverage on changed lines only. Good for PR gates.
Terminal for CI. HTML for local exploration of gaps.
coverage combine then coverage report after pytest-xdist runs.
Only if it contains logic. Empty __init__.py files are omitted.
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026