From qa-mutation-testing
Configures mutmut for Python mutation testing - `pip install mutmut`, runs via `mutmut run`, browses results via `mutmut browse` or `mutmut results`, applies surviving mutants to disk via `mutmut apply <id>`, suppresses with `# pragma: no mutate` annotations. Configures via `setup.cfg` / `pyproject.toml` with `source_paths` + per-test selection. Use for Python codebases needing mutation-quality verification of pytest / unittest suites.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-mutation-testing:mutmut-mutationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [mutmut-docs][md]:
Per mutmut-docs:
"Mutmut is a Python mutation testing system" with "a strong focus on ease of use." (mutmut-docs)
Key features per mutmut-docs:
Per mutmut-docs:
pip install mutmut
mutmut run
mutmut run "automatically detects test folders ('tests' or
'test') and locates source code" (mutmut-docs).
The first run is slow (full suite per mutant); subsequent runs use the cached state.
Per mutmut-docs, settings go in setup.cfg or
pyproject.toml:
[mutmut]
source_paths=src/
pytest_add_cli_args_test_selection=tests/
Or in pyproject.toml:
[tool.mutmut]
source_paths = ["src/"]
pytest_add_cli_args_test_selection = "tests/"
Common config:
| Setting | Use |
|---|---|
source_paths | Which files to mutate. |
pytest_add_cli_args_test_selection | Which tests to run per mutant. |
runner | pytest (default) or python -m unittest. |
tests_dir | Override auto-detected test directory. |
do_not_mutate | Regex of files / lines to skip. |
Per mutmut-docs, "Results are explored via mutmut browse,
where mutants can be retested or written to disk using mutmut apply <mutant>."
mutmut browse # interactive TUI
mutmut results # summary table
mutmut show <id> # show specific mutant diff
Output:
Total: 142
Killed: 119 (83.8%)
Survived: 23 (16.2%)
Timeout: 0
Suspicious: 0
Per mutmut-docs, common mutations include:
"Integer literals are changed by adding 1. So 0 becomes 1, 5 becomes 6, etc."
<becomes<=
breakconverts tocontinueand vice versa
Other mutators: arithmetic (+ → -), comparison flipping,
constant replacement, statement removal.
Per mutmut-docs:
Use code comments to skip specific areas:
# pragma: no mutate(single line)# pragma: no mutate block(indentation blocks)# pragma: no mutate start/end(arbitrary ranges)
def divide(a, b):
if b == 0: # pragma: no mutate
raise ZeroDivisionError("intentional unreachable")
return a / b
Use sparingly - each pragma is a confession that a line isn't mutation-tested. Reviewable in PRs.
Once a surviving mutant is identified, write a test that catches it. To verify the test catches this specific mutation:
mutmut apply <mutant-id> # writes the mutant to disk
pytest tests/affected_test.py # the new test should fail
git checkout src/ # revert the mutant
This proves the test catches the specific bug class.
- name: Mutation testing
if: github.event_name == 'schedule' # weekly
run: |
pip install -e '.[dev]'
pip install mutmut
mutmut run --max-children 4
- name: Surface results
if: always()
run: mutmut results > mutation-summary.txt
- uses: actions/upload-artifact@v4
if: always()
with:
name: mutation-results
path: mutation-summary.txt
For PRs, mutmut doesn't have native incremental mode; use
source_paths to scope to changed files via a wrapper script:
CHANGED=$(git diff --name-only origin/main...HEAD | grep '^src/')
mutmut run --paths-to-mutate "$CHANGED"
| Anti-pattern | Why it fails | Fix |
|---|---|---|
# pragma: no mutate as escape hatch | Hides untested code; defeats mutation testing. | Reserve pragmas for genuinely unreachable / untestable code (Step 5). |
| Running on every PR (full mutation) | Long; team disables. | Schedule weekly + per-PR scoped via wrapper (Step 7). |
Including third-party packages in source_paths | Mutates code you don't own. | Scope to project source only (Step 2). |
Skipping mutmut results in CI | No visibility into the score over time. | Pipe to artifact + dashboard. |
| Setting unrealistic mutation-score gates | Forces team to write low-value tests. | Start at current baseline; ratchet up by 1-2pp per quarter. |
pragma: no mutate for the specific
fixtures.setup.cfg / pyproject.toml, mutator examples (integer
literals, comparison flipping, break/continue).stryker-mutation,
stryker-net-mutation,
pitest-mutation,
mull-mutation - per-language
siblings.mutation-survivor-explainer - agent that suggests the missing test for survivors.Provides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.
npx claudepluginhub testland/qa --plugin qa-mutation-testing