Test-Impact Analysis¶
How the python-service golden path keeps CI feedback fast on pull requests by running only the tests actually impacted by the change, instead of the full suite on every push.
The mechanism¶
backstage/catalog/templates/python-service/skeleton/.github/workflows/ci.yml installs
pytest-testmon alongside the normal test dependencies
and branches CI behavior on event type:
- On pull requests: restores a
.testmondatacache (keyed on the PR's base branch and run ID, with fallback restore-keys so a fresh PR still gets a usable cache), then runspytest -v --testmon --junit-xml=junit.xml.pytest-testmontracks which lines each test actually exercises and skips any test whose covered lines weren't touched by the diff. Exit status5(no impacted tests selected — e.g. a docs-only PR) is treated as success, not a failure. - On push to
main(i.e. after merge): runs the full suite with the existing 70% coverage gate, unconditionally. This is the safety net — nothing merges without the complete suite passing at least once, so the PR-time speedup never lets a change through unverified.
This means the two-stage CI a service already had (PR checks → main push) didn't change shape; the PR stage just got selective instead of exhaustive.
Why this matters at scale¶
As a service's test suite grows toward hundreds or thousands of tests, running everything on every PR
becomes the dominant cost of iteration speed. pytest-testmon's coverage-based impact analysis means a
one-line change to a single module only re-runs the handful of tests that actually cover that module,
not the entire suite — while the full-suite run on main still catches anything the impact analysis
might have under-selected (e.g. changes to shared fixtures or dynamic imports that testmon's static
coverage mapping can miss).
Scope¶
This is currently wired into the python-service golden-path template only
(backstage/catalog/templates/python-service/skeleton/.github/workflows/ci.yml). Other language
templates (Node.js, Go, Ruby, JVM) do not yet have an equivalent impacted-tests step — if a team wants
this for another language, look for that ecosystem's coverage-based selective-test-runner (e.g. Nx's
affected-graph for JS/TS monorepos, or Gradle's built-in test-task input tracking for JVM) rather than
assuming pytest-testmon's approach ports directly.
Caching notes¶
The .testmondata cache is keyed per PR base branch and run ID, with restore-keys that fall back to
the most recent cache for that base branch, then any testmon- prefixed cache. A completely fresh
cache (first PR against a new base branch) simply means pytest-testmon treats every test as
potentially impacted for that one run — it self-corrects on the next cached run.