mutmut-mutation
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.
Install with skills.sh (any agent)
npx skills add testland/qa --skill mutmut-mutationmutmut-mutation
Overview
Per mutmut-docs (opens in new window):
"Mutmut is a mutation testing system for Python, with a strong focus on ease of use." (mutmut-docs (opens in new window))
Key features per mutmut-docs (opens in new window):
When to use
How to use
Step 1 - Install + first run
Per mutmut-docs (opens in new window):
pip install mutmut
mutmut runmutmut run "automatically detects test folders ('tests' or 'test') and locates source code" (mutmut-docs (opens in new window)).
The first run is slow (full suite per mutant); subsequent runs use the cached state.
Step 2 - Configure
Per mutmut-docs (opens in new window), 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. |
Step 3 - Browse results
Per mutmut-docs (opens in new window), "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 diffOutput:
Total: 142
Killed: 119 (83.8%)
Survived: 23 (16.2%)
Timeout: 0
Suspicious: 0Step 4 - Mutators
Per mutmut-docs (opens in new window), 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.
Step 5 - Suppress with pragmas
Per mutmut-docs (opens in new window):
Use code comments to skip specific areas:
def divide(a, b):
if b == 0: # pragma: no mutate
raise ZeroDivisionError("intentional unreachable")
return a / bUse sparingly - each pragma is a confession that a line isn't mutation-tested. Reviewable in PRs.
Step 6 - Apply a survivor
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 mutantThis proves the test catches the specific bug class.
Step 7 - CI integration
- 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.txtFor 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"Worked example
A team wants to verify the pytest suite for src/discounts.py before a release.
Pitfalls and limitations
Anti-patterns (pragma escape hatches, full-mutation-per-PR, third-party source_paths, unrealistic gates) and mutmut's limitations (slow runs, no native PR diff scoping, equivalent mutants) are catalogued in references/mutmut-pitfalls.md.
References
mutmut - anti-patterns and limitations
View source (opens in new window)mutmut - anti-patterns and limitations
Detailed pitfalls and constraints for mutmut-mutation. Linked inline from that skill's SKILL.md.
Anti-patterns
| 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. |
| Running on every PR (full mutation) | Long; team disables. | Schedule weekly + per-PR scoped via a wrapper script. |
Including third-party packages in source_paths | Mutates code you don't own. | Scope to project source only. |
Skipping mutmut results in CI | No visibility into the score over time. | Pipe to an artifact + dashboard. |
| Setting unrealistic mutation-score gates | Forces the team to write low-value tests. | Start at the current baseline; ratchet up by 1-2pp per quarter. |
Limitations
Related skills
mull-mutation
Runs Mull, the LLVM-IR mutation testing tool, against C/C++ test binaries built with Clang: covers install (the version-matched mull-NN package), the -fpass-plugin build flags for the Mull IR frontend, mull-runner invocation, the mutator catalog, path filtering, and GitHub Actions CI. Use when a C or C++ project needs mutation-score verification with the tool already chosen. Does not select among mutation tools and does not cover other languages (stryker-mutation for JS/TS, stryker-net-mutation for .NET, pitest-mutation for the JVM, mutmut-mutation for Python).
mutant-survival-triage
Normalizes a surviving-mutant record across StrykerJS, PIT, mutmut, and Mull into one shape, classifies why it survived (missing case, weak assertion, equivalent mutant, unreachable code, flaky killer), applies per-mutator heuristics for conditional-boundary, arithmetic-operator, statement-removal, and constant mutations, and drafts the specific test that would kill it. Treats equivalence as a judgment call, because deciding whether a mutant is equivalent to the original is undecidable in general, so a residual survivor rate is expected rather than a defect. Use when a mutation run has finished and the report lists surviving mutants that nobody has yet explained or turned into concrete test cases.
pitest-mutation
Configures PIT (PITest) for mutation testing of JVM projects (Java, Kotlin via the Kotlin plugin) - wires the `pitest-maven` or `pitest-gradle-plugin` with `mutationThreshold`, `coverageThreshold`, target classes/tests filtering, runs `mvn pitest:mutationCoverage`, parses the HTML + XML reports. Use when the JVM suite needs mutation-quality verification - the canonical Java mutation testing tool, fast (PIT analyzes "in minutes rather than days").
stryker-mutation
Configures StrykerJS for mutation testing of JavaScript / TypeScript / React / Vue / Svelte / Node - picks the test-runner plugin (`@stryker-mutator/jest-runner`, `mocha-runner`, `vitest-runner`, `karma-runner`), authors `stryker.conf.json` with mutate globs + thresholds, runs incremental mode for PRs (only mutate changed files), and reports the mutation score. Use when a JS/TS test suite has ≥80% line coverage and the team wants to verify the tests actually catch bugs (not just touch lines).
stryker-net-mutation
Configures Stryker.NET for mutation testing of .NET Core / .NET Framework projects - installs `dotnet-stryker` global tool, scopes mutation to specific csproj, supports xUnit / NUnit / MSTest, authors `stryker-config.json` with thresholds, runs in CI. Use when a .NET test suite needs mutation-quality verification - closes the .NET ecosystem gap left by Stryker.NET being newer than the JS variant.