How to Measure Code Coverage with pytest-cov
pytest-cov is a pytest plugin that wraps coverage.py, integrating coverage measurement directly into your test run. Instead of running coverage run -m pytest as a separate command, pytest-cov lets you pass --cov to pytest and get coverage output alongside test results in a single invocation. This is a convenience for users who prefer a tighter pytest workflow. For a deeper foundation in coverage.py itself, see Setting up testing with pytest and uv (which demonstrates coverage.py directly).
Install pytest-cov
Add it as a dev dependency with uv:
uv add --dev pytest-covRun tests with coverage
Pass --cov with the path to your source code:
uv run pytest --cov=src/mypackagepytest-cov appends a coverage summary to the test output:
$ uv run pytest --cov=src/mypackage
========== tests coverage ==========
Name Stmts Miss Cover
--------------------------------------------------
src/mypackage/__init__.py 2 0 100%
src/mypackage/core.py 45 12 73%
--------------------------------------------------
TOTAL 47 12 74%
Add --cov-report=term-missing to see which lines lack coverage:
uv run pytest --cov=src/mypackage --cov-report=term-missingThe Missing column shows the exact line numbers your tests did not execute.
Configure coverage in pyproject.toml
Typing --cov flags on every run gets tedious. Move them into pyproject.toml so coverage runs automatically:
[tool.pytest.ini_options]
addopts = "--cov=src/mypackage --cov-report=term-missing"coverage.py reads its own settings from [tool.coverage.*] sections in the same file:
[tool.coverage.run]
source = ["src/mypackage"]
branch = true
[tool.coverage.report]
show_missing = trueSetting branch = true measures whether both sides of every if/else branch execute, not just whether each line runs. Branch coverage catches dead branches that statement coverage misses.
Enforce a coverage threshold
Add fail_under to the report configuration:
[tool.coverage.report]
fail_under = 80When total coverage drops below 80%, pytest exits with a nonzero status code. CI pipelines that check the exit code will fail the build automatically.
The same option is available as a CLI flag:
uv run pytest --cov-fail-under=80Generate an HTML report
The terminal summary shows which lines are missing, but an HTML report lets you click through each file and see uncovered lines highlighted in red:
uv run pytest --cov-report=htmlThis creates an htmlcov/ directory. Open htmlcov/index.html in a browser to browse the results.
Add htmlcov/ to .gitignore so generated reports stay out of version control:
htmlcov/To generate both the terminal summary and an HTML report in a single run:
uv run pytest --cov-report=term-missing --cov-report=htmlAdd coverage to GitHub Actions
A workflow step that runs pytest-cov enforces coverage on every push:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v8.1.0
- run: uv run pytest --cov --cov-report=term-missingIf fail_under is set in pyproject.toml, the step fails when coverage drops below the threshold. No extra CI configuration is needed.
Learn More
- How to run tests using uv covers filtering, output control, and persistent pytest defaults
- How to run tests in parallel with pytest-xdist distributes tests across CPU cores (works alongside pytest-cov)
- Essential pytest plugins covers four plugins that complement coverage measurement
- pytest-cov documentation for the full list of CLI options and report types
- coverage.py configuration reference for every
[tool.coverage.*]setting