Testland
Browse all skills & agents

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-mutation
View source

mutmut-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):

  • "Found mutants can be applied on disk with a simple command making it very easy to work with the results"
  • "Remembers work that has been done, so you can work incrementally"
  • "Knows which tests to execute, speeding up mutation testing"
  • "Interactive terminal based UI"
  • "Parallel and fast execution"

When to use

  • A Python codebase has a pytest / unittest suite with high coverage and the team wants to verify the assertions.
  • A specific module needs mutation-quality verification before shipping a release.
  • The team is debating "should we add another test?" - surviving mutants tell you where.

How to use

  1. pip install mutmut and confirm the pytest / unittest suite is green before mutating.
  2. Scope the run in setup.cfg or pyproject.toml (source_paths + test selection) so only project source is mutated.
  3. Run mutmut run - the first run is slow; state is cached so later runs are incremental.
  4. Read the score with mutmut results; open survivors interactively with mutmut browse.
  5. For each survivor, write a test that kills it; verify with mutmut apply <id>, re-run that test, then revert.
  6. Reserve # pragma: no mutate for genuinely unreachable lines only.
  7. Wire a weekly full run plus PR-scoped runs into CI, uploading mutmut results as an artifact.

Step 1 - Install + first run

Per mutmut-docs (opens in new window):

pip install mutmut
mutmut run

mutmut 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:

SettingUse
source_pathsWhich files to mutate.
pytest_add_cli_args_test_selectionWhich tests to run per mutant.
runnerpytest (default) or python -m unittest.
tests_dirOverride auto-detected test directory.
do_not_mutateRegex 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 diff

Output:

Total: 142
Killed: 119 (83.8%)
Survived: 23 (16.2%)
Timeout: 0
Suspicious: 0

Step 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 <=

break converts to continue and 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:

  • # 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.

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 mutant

This 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.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"

Worked example

A team wants to verify the pytest suite for src/discounts.py before a release.

  1. Scope the run in setup.cfg:
    [mutmut]
    source_paths=src/discounts.py
    pytest_add_cli_args_test_selection=tests/test_discounts.py
    
  2. mutmut run, then mutmut results:
    Total: 142
    Killed: 119 (83.8%)
    Survived: 23 (16.2%)
    
  3. mutmut browse shows a survivor: the boundary if total > 100: mutated to if total >= 100: - the suite never exercises total == 100.
  4. Add a test for the no-discount path at total == 100, then confirm it catches the mutant:
    mutmut apply <id>                 # id from the browse listing
    pytest tests/test_discounts.py    # now fails on the mutated line
    git checkout src/                 # revert the mutant
    
  5. Re-running mutmut run shows the mutant killed and the score up by one mutant.

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

  • md (opens in new window) - mutmut overview: ease of use, incremental work, intelligent test selection, parallel execution, configuration via setup.cfg / pyproject.toml, mutator examples (integer literals, comparison flipping, break/continue).
  • stryker-mutation, stryker-net-mutation, pitest-mutation, mull-mutation - per-language siblings.

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-patternWhy it failsFix
# pragma: no mutate as escape hatchHides 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_pathsMutates code you don't own.Scope to project source only.
Skipping mutmut results in CINo visibility into the score over time.Pipe to an artifact + dashboard.
Setting unrealistic mutation-score gatesForces the team to write low-value tests.Start at the current baseline; ratchet up by 1-2pp per quarter.

Limitations

  • Slow. Even with parallel execution, full mutation runs take 10-60 min on medium codebases.
  • No native PR diff scoping. Use the wrapper pattern: git diff -> --paths-to-mutate.
  • Test framework hooks. Some pytest fixtures interact oddly with mutated code; use pragma: no mutate for the specific fixtures.
  • Equivalent mutants. Some mutations produce semantically identical code; impossible to kill. Identify and exclude.

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.