Test Coverage and CI
Measure what the suite executes, set meaningful thresholds, and run the same checks automatically on every change.
By the end, you’ll be able to…
- Read line and branch coverage reports.
- Set a threshold that protects critical code.
- Write a CI workflow that installs, tests, and reports.
- Keep quality gates honest as the project grows.
01Coverage measures execution
Coverage records which lines or branches the test suite executed. It does not prove that the assertions are meaningful. A line can run during a test that never checks its result. Use coverage to find unexercised paths, then write a test that asserts the behavior of those paths.
Line coverage is the common starting point; branch coverage is stronger because it distinguishes both sides of a condition. A report with 100% line coverage can still miss an else branch that never ran. Read the missing-line report, not only the percentage.
py -m pip install pytest pytest-covpy -m pytest --cov=myapp --cov-report=term-missingpy -m pytest --cov=myapp --cov-report=html02Set thresholds that mean something
A fail-under threshold prevents a merge from lowering coverage below an agreed level. Choose the threshold from the current baseline and raise it deliberately. A project-wide number can hide a critical module with poor coverage, so review per-module reports for important boundaries.
Exclude generated code, migration files, and trivial configuration from the report rather than writing tests that only inflate a number. Enforce branch coverage where decisions matter. The goal is confidence in behavior, not a perfect percentage.
[tool.coverage.run]branch = truesource = ["myapp"][tool.coverage.report]fail_under = 85show_missing = trueskip_covered = true03CI reproduces a clean environment
Continuous integration runs the project's checks in a fresh environment on every push or pull request. A GitHub Actions workflow checks out the code, installs the supported Python version, installs dependencies, and runs the same commands a developer runs locally. If CI needs a secret step that developers cannot reproduce, the workflow is hiding a requirement.
Use a matrix to test supported Python versions, and cache dependencies only when the cache key includes the lock file. Upload the coverage report or test results as an artifact so a failure can be inspected without rerunning the job.
name: teston: push: pull_request:jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.12", "3.13"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: pip - run: python -m pip install -r requirements.txt - run: python -m pytest --cov=myapp --cov-report=term-missing04Keep quality gates honest
A red check must block the merge; a check that is routinely ignored is worse than no check because it teaches the team to ignore signals. Keep the required set small: tests, type checking, and one formatter or linter. Add a check when it has caught a real class of defect.
Flaky tests erode trust. Quarantine a flaky test, find the shared state or timing assumption, and fix it. Do not add retries to hide nondeterminism. A test suite that passes for the wrong reason is a liability.
The ideas worth keeping.
- Coverage finds untested paths; it does not prove correctness.
- Set thresholds from a baseline and review critical modules separately.
- CI should run the same reproducible commands developers run locally.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Configure coverage
Add pytest-cov configuration that enables branch coverage, fails below 85%, and prints missing lines. Use a pyproject.toml table.
Explore a worked solution
[tool.coverage.run]branch = truesource = ["myapp"][tool.coverage.report]fail_under = 85show_missing = trueBranch coverage checks both sides of decisions. The threshold turns the report into an enforceable gate, and show_missing points to the next test worth writing.
Write a test workflow
Create a GitHub Actions workflow that runs on pushes and pull requests, installs Python 3.12, installs requirements, and runs pytest with coverage.
Explore a worked solution
name: teston: [push, pull_request]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: python -m pip install -r requirements.txt - run: python -m pytest --cov=myapp --cov-report=term-missingThe workflow uses the same interpreter and package installation path as a developer. Running it on every pull request makes the gate visible before merge.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Configure coverage
Add pytest-cov configuration that enables branch coverage, fails below 85%, and prints missing lines. Use a pyproject.toml table.
[Process exited with code 0 - No output]
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
One lesson closer.
Reading is a start. Trying it yourself makes the connection.