Python · Comparison
Python coverage: coverage.py, SlipCover, and Supercov
Compare Python coverage tools: coverage.py with pytest-cov, SlipCover, and Supercov. See per-test contexts, MC/DC, and assertion coverage side by side.
Start with pytest-cov for line and branch coverage
For an existing pytest project, coverage.py with pytest-cov is a straightforward starting point. pytest-cov connects coverage.py to pytest; it is not a second measurement engine.
Install it in your project’s Python environment, then measure your source:
python -m pip install pytest-cov
python -m pytest --cov=src --cov-branch --cov-report=term-missing
Replace src with your package or source directory. The report lists missed
lines and partial branches. coverage.py’s
branch guide explains
how it records transitions between lines.
To associate execution with individual pytest tests, add --cov-context=test.
pytest-cov supports per-test contexts,
including setup, run and teardown. Supercov is not unique simply because it
connects code with tests.
If your project uses unittest, you can keep that runner:
python -m coverage run --branch --source=src -m unittest discover
python -m coverage report -mWhere SlipCover fits
SlipCover is an alternative line and branch collector. It can launch pytest without a separate pytest coverage plugin:
python -m pip install slipcover
python -m slipcover --branch --source src -m pytest
Its documentation explains the collection approach and supported environments. If coverage overhead is your problem, compare it on your own suite with the same Python version, source scope and outputs. The small example below is not a speed test.
Run the same pytest suite with all three tools
Download the comparison and open
its python folder. Use CPython 3.9 or newer; the recording used 3.13.1.
Create a virtual environment and install the pinned packages:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
On Windows, activate with .venv\\Scripts\\Activate.ps1 instead. The requirements
file belongs to this downloadable example, not to your existing project.
Its source is:
def checkout(signed_in, expired, price, quantity):
if signed_in and not expired:
total = price * quantity
return {"status": "confirmed", "total": total}
return {"status": "denied", "total": 0}
Two pytest tests check the returned status for a valid session and a signed-out visitor. Neither checks an expired session or the calculated amount.
python -m pytest --cov=src --cov-branch --cov-context=test --cov-report=term-missing
python -m slipcover --branch --source src -m pytest
supercov -- python -m pytest
supercov runs latest
The coverage.py report includes:
Name Stmts Miss Branch BrPart Cover Missing
------------------------------------------------------------
src/session.py 5 0 2 0 100%
------------------------------------------------------------
TOTAL 5 0 2 0 100%
Here are the recorded results. coverage.py and SlipCover ran on September 14, 2026; Supercov’s runs were repeated with 2.0.0 on September 25 and matched. Every run uses the same source and pytest tests.
| Report | Lines | Branches | MC/DC |
|---|---|---|---|
| coverage.py 7.16.0 through pytest-cov 7.1.0 | 100% (5/5) | 100% (2/2) | Not reported |
| SlipCover 1.1.0 | 100% (5/5) | 100% (2/2) | Not reported |
| Supercov 2.0.0 wrapping pytest | 100% (5/5) | 100% (6/6) | 50% (1/2) |
The tools use different branch models, hence the different denominators. That does not make one branch count more correct. MC/DC supplies a separate measurement: whether each condition independently changes the decision.
Find the missing expiry test
Query the checkout decision:
supercov runs latest decision src/session.py:2
The output includes:
signed_in and not expired
C1 covered: signed_in
C2 MISSING: not expired
confidence executed
vectors:
TT -> T tests=1 confidence=executed
F- -> F tests=1 confidence=executed
The signed-out test reaches the denied branch without evaluating the expiry condition. A signed-in customer with an expired session is the missing case:
def test_rejects_an_expired_session():
assert checkout(True, True, 25, 2)["status"] == "denied"
With that third case, the valid-session test and expired-session test show the expiry condition changing the result. The MC/DC guide walks through the comparison.
Check the amount, not just the status
A returned status does not establish that the total is right. The original tests still pass if the calculation subtracts 1 from the answer. A stronger assertion in the valid-session test is:
assert checkout(True, False, 25, 2) == {"status": "confirmed", "total": 50}
The download includes the completed suite in completed-tests.txt. Replace
tests/test_session.py with its contents and rerun the same commands.
| Check | Starter tests | Completed tests |
|---|---|---|
| Tests against the original code | Pass: 2 tests | Pass: 3 tests |
| Native line and branch coverage | 100% | 100% |
| MC/DC | 50% (1/2) | 100% (2/2) |
| Remove the expiry guard | Still pass | Fail |
| Subtract 1 from the total | Still pass | Fail |
Both faults were run separately. This demonstrates two specific missing checks, not a general mutation score. The recording includes commands, full output, source and tests.
Assertions were not assessed here (the comparison was recorded with Supercov
2.0), so it reports no assertion-coverage percentage.
Assertion coverage is a separate step:
runs latest assertions assess asks Jev which executed statements each test
would catch a change to.
Which tool should you keep?
Use coverage.py and pytest-cov when line, branch and test-context reports answer your question. Consider SlipCover when evaluating collection overhead. Supercov adds independent-condition gaps and an assertion-review workflow without replacing pytest.
| Feature | coverage.py7.16.0 | SlipCover1.1.0 | Supercov2.0.0 |
|---|---|---|---|
| Code quality scoring | Not built in | Not built in | Supported |
| Security review | Not built in | Not built in | Supported |
| Line coverage | Supported | Supported | Supported |
| Branch coverage | Supported | Supported | Supported |
| MC/DC | Not built in | Not built in | Supported |
| Per-test coverage | Supported | Not built in | Supported |
| Assertion coverage | Not built in | Not built in | Supported |
| Reports | Supported | Not built in | Supported |
✓ Supported · × Not built in. Standard coverage workflows in the versions shown; custom plugins are outside this table.
Supercov measures CPython 3.9 and newer. pytest-cov uses coverage.py; enable test contexts for per-test coverage.
Per-test coverage means individual tests, not just test files. Supercov needs a supported runner. Assertion coverage is Jev’s judgment, test by test, of whether changing each executed statement would make the test fail; it is not a mutation score or a correctness guarantee. Security review comes from Supercov’s Jev checks; reports means a browsable HTML report from the tool itself.
Supercov measures CPython 3.9 or newer. See the Python installation guide. It is not the only Python MC/DC option: pymcdc is a dedicated analyzer.
None of these coverage percentages prove that the application meets its requirements. For checks outside source coverage, compare QA tools for coding agents.
Example evidence: source, commands and full results (JSON) · runnable examples (ZIP). Measured September 14, 2026; Supercov runs repeated with 2.0.0 on September 25, unchanged. Same-runner fixtures, not a performance benchmark or a general conformance test.
Feature sources
- coverage.py: Coverage and reports
- coverage.py: HTML report
- coverage.py: Test contexts
- coverage.py: pytest-cov integration
- SlipCover: Coverage features
- Supercov: Coverage model
- Supercov: Supported runners
- Supercov: Assertion coverage
- Supercov: Code quality
- Supercov: Security
- Supercov: Reports
- Supercov: CLI reference