coverage-py-analysis
Configures coverage.py for Python projects - wires `coverage run` (replacing `python` for instrumentation), enables branch coverage via the `--branch` flag or `branch = True` config, manages the `.coverage` data file (single-process and `combine` for parallel pytest-xdist runs), authors `.coveragerc` with `source` / `omit` / `fail_under`, and emits the format the downstream tool needs (`coverage report` for terminal, `coverage xml` for Cobertura, `coverage html` for human review, `coverage lcov` for SaaS, `coverage json` for programmatic post-processing). Use for any Python test stack (pytest, unittest, nose) that needs PR-time coverage signal.
Install with skills.sh (any agent)
npx skills add testland/qa --skill coverage-py-analysiscoverage-py-analysis
Overview
Per coveragepy-docs (opens in new window):
"Coverage.py is a tool for measuring code coverage of Python programs. It monitors your program, noting which parts of the code have been executed, then analyzes the source to identify code that could have been executed but was not."
The tool is the de facto Python coverage solution; pytest's pytest-cov plugin is a thin convenience wrapper around it. As of the source fetch on 2026-05-05, "Current version is 7.13.5 (March 2026), supporting Python 3.10-3.15 alpha and PyPy3" (coveragepy-docs (opens in new window)).
When to use
Step 1 - Install + replace python with coverage run
pip install coverage[toml][toml] is needed only for older Python (<3.11); newer ones include TOML parsing in stdlib.
Per coveragepy-docs (opens in new window):
"Replace your normal python command with this tool (e.g.,
python something.pybecomescoverage run something.py)."
In practice, run pytest under coverage:
coverage run -m pytest
coverage reportOr via pytest-cov:
pytest --cov=src --cov-branch --cov-report=term-missing --cov-report=xml --cov-report=lcovStep 2 - Enable branch coverage
Per coveragepy-docs (opens in new window), coverage.py defaults to statement coverage (line coverage). Branch coverage requires opt-in:
coverage run --branch -m pytestOr in .coveragerc:
[run]
branch = TrueBranch coverage catches the case where every line is executed but not every condition arm - if x and y where only the true branch is tested.
Step 3 - Author .coveragerc
The canonical config (.coveragerc or [tool.coverage] in pyproject.toml):
[run]
source = src
branch = True
parallel = True
omit =
*/tests/*
*/migrations/*
*/conftest.py
[report]
fail_under = 80
show_missing = True
skip_covered = False
exclude_lines =
pragma: no cover
raise NotImplementedError
if __name__ == .__main__.:
[xml]
output = coverage.xml
[html]
directory = htmlcov
[lcov]
output = coverage.lcov
[json]
output = coverage.jsonPer coveragepy-docs (opens in new window), the four key [run] settings:
| Setting | Use |
|---|---|
source | Restricts coverage to specific paths (avoids inflating from third-party). |
branch | Enables branch coverage (Step 2). |
omit | Excludes files (tests, migrations, generated code). |
fail_under | Fails coverage report if the total drops below the threshold. |
exclude_lines patterns let the team mark unreachable / not-meant-to-be-tested code with magic comments (# pragma: no cover) and sentinel patterns like raise NotImplementedError.
Step 4 - Combine parallel runs
Pytest-xdist runs tests across multiple processes; each process writes its own .coverage.<host>.<pid>.<rand> file. Per coveragepy-docs (opens in new window), coverage combine merges them:
coverage run --parallel -m pytest -n auto
coverage combine
coverage report
coverage xml
coverage lcov--parallel (or parallel = True in .coveragerc) makes coverage write per-process files instead of overwriting .coverage. coverage combine then merges them into the final .coverage.
Without combine, only the last process's data survives - the most common new-user mistake.
Step 5 - Pick the output format
Per coveragepy-docs (opens in new window), coverage.py emits five formats:
| Command | Output | Use |
|---|---|---|
coverage report | Terminal text | CI log readability + dev loop. |
coverage html | htmlcov/index.html | Human review with per-line drill-down. |
coverage xml | coverage.xml (Cobertura format) | Jenkins, Azure DevOps; cross-tool aggregation. |
coverage lcov | coverage.lcov | Codecov, Coveralls, cross-tool diffing. |
coverage json | coverage.json | Programmatic post-processing. |
A typical CI emits xml + lcov + report:
coverage xml # for Jenkins
coverage lcov # for Codecov
coverage report # for the CI logStep 6 - coverage report --fail-under
For a self-contained gate (without external scripting):
coverage report --fail-under=80Or per the .coveragerc [report] fail_under = 80 setting. Exit code is non-zero if total coverage is below; CI fails.
For per-file gates (the same pattern as jest-coverage-analysis), parse the JSON output:
# scripts/per_file_gate.py
import json, sys
CRITICAL_PATHS = {
'src/api/payments.py': {'lines': 100, 'branches': 100},
'src/api/auth.py': {'lines': 95, 'branches': 90},
}
data = json.load(open('coverage.json'))
failures = []
for path, requirements in CRITICAL_PATHS.items():
f = data.get('files', {}).get(path)
if not f:
failures.append(f"{path}: file not found in coverage report")
continue
line_pct = f['summary']['percent_covered']
branch_pct = f['summary'].get('percent_covered_branches', 100)
if line_pct < requirements['lines']:
failures.append(f"{path}: line% {line_pct:.1f} < {requirements['lines']}")
if branch_pct < requirements['branches']:
failures.append(f"{path}: branch% {branch_pct:.1f} < {requirements['branches']}")
if failures:
print('\n'.join(failures))
sys.exit(1)Per-file gates beat global gates for the same reason as in Jest: critical paths get a strict floor; the rest gets a refactor-friendly global.
Step 7 - # pragma: no cover discipline
exclude_lines lets the team annotate unreachable code:
def divide(a, b):
if b == 0: # pragma: no cover
raise ZeroDivisionError("intentional unreachable")
return a / bUse sparingly. Each pragma: no cover is a confession that the code is excluded from coverage - make sure the exclusion is intentional and reviewable.
The default exclude_lines patterns (Step 3) auto-exclude raise NotImplementedError and if __name__ == "__main__": blocks that are typically untested boilerplate.
Step 8 - CI shape
- uses: actions/setup-python@v5
with:
python-version: '3.13'
- run: pip install -e '.[dev]'
- name: Run tests with coverage (parallel)
run: |
coverage run --parallel -m pytest -n auto
coverage combine
- name: Emit reports
run: |
coverage xml
coverage lcov
coverage json
coverage report --fail-under=80
- name: Per-file gate
run: python scripts/per_file_gate.py
- name: Upload to dashboard
uses: codecov/codecov-action@v5
with:
files: coverage.lcov
- name: Save baseline (main only)
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v4
with:
name: coverage-baseline
path: coverage.lcovAnti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Forgetting coverage combine after parallel runs | Only one process's data survives; coverage drops mysteriously. | Always combine after --parallel (Step 4). |
Not setting source = src | Coverage measures every Python file imported, including stdlib + deps; numbers meaningless. | Set source to the project's own code (Step 3). |
Statement coverage only (no --branch) | Misses missing branch arms; correctness regressions invisible. | Enable branch coverage globally (Step 2). |
# pragma: no cover as escape hatch for "I'm too lazy to test this" | Coverage number stays high; risk hidden. | Reserve pragmas for truly unreachable / untestable; review each addition. |
| Running coverage in production / staging | Instrumentation overhead; coverage's tracer slows the program. | Coverage is for tests only. |
Forgetting to omit tests/ from source | Tests count as covered code; aggregate inflated. | Add tests/ to omit (Step 3) or restrict source to src/. |
pytest-cov without --cov-branch | Same as above - statement-only coverage. | Always pass --cov-branch. |
Per-process --cov-report=html in xdist runs | Each worker writes a partial HTML; the report is incomplete. | Generate reports after combine, not during pytest-cov. |
Limitations
References
Related skills
allure-reports
Configures Allure Report (test-runner adapter install, `allure-results` directory wiring, `categories.json` for failure classification, `history-trend.json` retention via the copy-history-between-runs pattern), runs the Allure CLI to convert `allure-results` to a static HTML site, and uploads the report as a CI artifact. Use when the team needs richer test reporting than JUnit XML - step-level attachments, per-test history, retry tracking, and severity / epic / feature labeling across framework-agnostic adapters (pytest, Jest, JUnit, TestNG, NUnit, Mocha). As a rich static HTML report generator, it is the open-source alternative to extentreports (JVM/.NET per-test HTML narrative); for hosted cross-run flakiness analytics rather than a static per-run report use currents-integration.
cobertura-analysis
Parses Cobertura XML coverage reports (the JVM-canonical format originally from the cobertura-cobertura tool, also emitted by JaCoCo `--coverage-xml`, coverage.py `--xml`, Istanbul / Jest `cobertura` reporter, gocover-cobertura, and dotnet's `coverlet`). Walks the coverage-04 DTD structure (coverage → packages → classes → methods → lines + conditions), computes per-file deltas, and emits PR-time gating verdicts. Use when the existing CI emits Cobertura XML - typical for JVM-heavy stacks and tools that ship Cobertura as a default reporter.
coverage-diff-reporter
Builds a per-PR coverage delta report from any pair of LCOV / Cobertura / JSON coverage outputs (current run + baseline from the merge target) - emits a per-file table with line% / branch% deltas, called-out new files, hidden drops (overall +0.1pp but one file -8pp), and a single-line PR-comment summary. Use when the team has coverage in CI but needs human-readable PR feedback that points at the specific file the reviewer should focus on, not just an aggregate number.
currents-integration
Wires Currents.dev cross-run test analytics into a Playwright suite: installs `@currents/playwright`, authors `currents.config.ts` (env-sourced `recordKey` + `projectId`), registers `currentsReporter()`, enables trace/video/screenshot artifacts, and runs via `npx pwc` so per-test traces stream to the Currents dashboard with over-time flakiness, slowest-test, and pass-rate trends. Use when a Playwright suite needs hosted cross-run suite-health analytics; for a static per-run report use extentreports or allure-reports, and to sync results into Jira test management use zephyr-integration or xray-integration.
extentreports
Configures ExtentReports v5 for a JVM (or .NET via `extentreports-dotnet`) test run: wires `ExtentSparkReporter`, `attachReporter`, `createTest`, the `info`/`pass`/`warning`/`skip`/`fail` log chain, screenshots via `MediaEntityBuilder`, hierarchical `createNode` parent/child tests, and category/author/device labels, emitting a static HTML report alongside JUnit XML for CI artifact upload. Use when a suite on the Aventstack ExtentReports stack wants a richer per-test HTML narrative than JUnit XML gives; for code-coverage reporting use jacoco-analysis, and for hosted cross-run flakiness analytics use currents-integration.
jacoco-analysis
Configures JaCoCo for JVM projects (Java / Kotlin / Scala / Groovy) - wires the runtime agent via `jacoco-maven-plugin` `prepare-agent`, generates per-build reports (HTML / XML / CSV) via the `report` goal, gates the build via the `check` goal with element / limit / minimum rules, parses the six native counters (instructions, branches, lines, methods, classes, cyclomatic complexity), and converts JaCoCo XML to LCOV / Cobertura when downstream tools need a different format. Use when the JVM build is Maven / Gradle and the team wants the canonical JVM coverage tool - or to convert JaCoCo output for cross-language coverage aggregation.
jest-coverage-analysis
Configures Jest's built-in coverage (Istanbul-instrumented `babel` provider or V8-native `v8` provider), wires the right `coverageReporters` for downstream consumption (`lcov` for SaaS / cross-tool, `cobertura` for Jenkins, `text-summary` for terminal, `html` for human review), authors per-file `coverageThreshold` rules that focus the gate on critical paths (vs the global-only foot-gun), and parses the per-file JSON output for PR-time deltas. Use when the project tests with Jest (or Vitest, which uses the same Istanbul/V8 provider) and the team needs PR-time coverage signal that's both local-runnable and CI-gateable.
junit-xml-analysis
Parses JUnit-format XML reports (the de-facto interchange format every CI ingests - Jenkins, GitHub Actions, GitLab, Buildkite, CircleCI) into structured, machine-readable per-suite and per-case metrics tables (passed / failed / errored / skipped, time, classname, message, stack), groups failures by classname for trend analysis, and distinguishes "new failures vs flakes" by cross-referencing the `flakyFailure` and `rerunFailure` rerun elements. Use when the downstream consumer is a dashboard, script, or aggregator - not when the goal is a human-readable prose summary (use test-run-summary-author for that). Single-run, in-XML aggregation only; for cross-run cross-environment roll-ups, use a cross-run test-suite aggregator.
lcov-analysis
Parses LCOV `.info` text files (the de-facto coverage interchange format produced by gcov, llvm-cov, Coverage.py via `py2lcov`, JaCoCo via `xml2lcov`, Devel::Cover, Jest via `lcov` reporter, NYC, and most others). Extracts per-file line / function / branch metrics from the canonical record keywords (TN/SF/FN/FNDA/FNF/FNH/BRDA/BRF/BRH/DA/LH/LF), computes the diff vs a baseline, and emits per-file gating verdicts. Use for PR coverage gates that don't depend on a specific language runtime.
test-coverage-targeter
Builds a "what to test next" recommendation by combining a coverage report (LCOV / Cobertura / coverage.py JSON / Jest JSON / JaCoCo XML) with the PR's `git diff`, ranking uncovered branches by risk × cost - risk weighted by McCabe cyclomatic complexity and code-churn frequency, cost weighted by the unit-test pyramid layer (unit tests cheaper than integration than E2E). Emits a prioritized list with concrete file:line targets and the test layer recommended for each. Use when a team has the budget to write 5 - 10 new tests and needs help picking which uncovered code to target first instead of blindly chasing 100% coverage.
test-run-summary-author
Build-an-X workflow that turns a structured test-run artifact (JUnit XML, Allure JSON, TestRail / Xray / Zephyr export) plus optional release context (version, build URL, deploy target) into a narrative markdown summary for release notes, an exec status update, or a stand-up Slack post. Distinct from the per-framework parsers junit-xml-analysis / allure-reports / coverage-diff-reporter, which emit structured tabular reports: this skill takes the same data and writes the human-readable narrative. Use when a manager needs a draft release note or stand-up summary from a single run; for cross-run trend analytics use currents-integration.
testrail-integration
Syncs test runs / results / cases between an automated test suite and TestRail (Gurock / Idera) - opens a Test Run for the build (`add_run`), batches per-case results back via `add_results_for_cases` (preferred over per-test `add_result_for_case` - N+1 API calls vs 1), maps the test framework's pass/fail/skip to TestRail status IDs, and attaches build URL + version + elapsed time. Use when the team's test management is standalone TestRail (not a Jira app) and automated suites must update it without a human copy-paste step; when the TCM is instead a Jira app use xray-integration (Xray) or zephyr-integration (Zephyr Scale), and for hosted cross-run flakiness analytics rather than TCM sync use currents-integration.
xray-integration
Imports CI test results into Xray for Jira - authenticates via the `client_id` + `client_secret` → JWT exchange (Cloud) or PAT / Basic (Server), posts to the format-specific `/api/v2/import/execution/*` endpoint (`/junit` for JUnit XML, `/cucumber` for Cucumber JSON, `/nunit` / `/testng` / `/robot` for the others), and maps automated results to existing Xray Test issues via the `xray-junit-extensions` `@XrayTest(key="...")` annotation. Use when the team uses the Xray Jira app to manage Test, Test Set, and Test Execution issue types and CI must keep those execution issues in sync; for the other Jira TCM app use zephyr-integration (Zephyr Scale), for standalone non-Jira TestRail use testrail-integration, and for hosted cross-run flakiness analytics rather than TCM sync use currents-integration.
zephyr-integration
Syncs automated test results to Zephyr Scale for Jira (formerly TM4J / SmartBear / Adaptavist): picks the product variant (Scale Cloud / Squad / Enterprise), authenticates with a long-lived API token as a Bearer header, opens a Test Cycle per build, posts executions via `POST /testexecutions` (or bulk JUnit via `/automations/executions/junit`), and maps test methods to Zephyr Test Cases via `@TestCaseKey`-style annotations. Use when the team's Jira test management is Zephyr Scale; for the Xray Jira app use xray-integration, for standalone TestRail use testrail-integration, and for over-time flakiness analytics rather than TCM sync use currents-integration.