---
title: "mutmut - python mutation tester — mutmut  documentation"
section: "raw"
type: "source"
created: "2026-08-27"
updated: "2026-08-27"
canonical: "https://pyweb.dev/wiki/raw/articles/mutmut-documentation-2026"
---
# mutmut - python mutation tester — mutmut  documentation

# mutmut - python mutation tester [¶](https://mutmut.readthedocs.io/en/latest/\#mutmut-python-mutation-tester "Link to this heading")

[![https://github.com/boxed/mutmut/actions/workflows/tests.yml/badge.svg](https://github.com/boxed/mutmut/actions/workflows/tests.yml/badge.svg)](https://github.com/boxed/mutmut/actions/workflows/tests.yml)  [![Documentation Status](https://readthedocs.org/projects/mutmut/badge/?version=latest)](https://mutmut.readthedocs.io/en/latest/?badge=latest)

Mutmut is a mutation testing system for Python, with a strong focus on ease
of use. If you don’t know what mutation testing is try starting with
[this article](https://kodare.net/2016/12/01/mutmut-a-python-mutation-testing-system.html).

Some highlight features:

- 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


![_images/browse_screenshot.png](https://mutmut.readthedocs.io/en/latest/_images/browse_screenshot.png)

If you want to mutate code outside of functions, you can try using mutmut 2,
which has a different execution model than mutmut 3+.

## Requirements [¶](https://mutmut.readthedocs.io/en/latest/\#requirements "Link to this heading")

Mutmut must be run on a system with fork support. This means that if you want
to run on windows, you must run inside WSL.

## Install and run [¶](https://mutmut.readthedocs.io/en/latest/\#install-and-run "Link to this heading")

You can get started with a simple:

```
pip install mutmut
mutmut run
```

This will run pytest on tests in the “tests” or “test” folder and
it will try to figure out where the code to mutate is.

You can stop the mutation run at any time and mutmut will restart where you
left off.

To work with the results, use mutmut browse where you can see the mutants,
retest them when you’ve updated your tests.

You can also write a mutant to disk from the browse interface, or via
mutmut apply <mutant>. You should **REALLY** have the file you mutate under
source code control and committed before you apply a mutant!

If during the installation you get an error for the libcst dependency mentioning the lack of a rust compiler on your system, it is because your architecture does not have a prebuilt binary for libcst and it requires both rustc and cargo from the [rust toolchain](https://www.rust-lang.org/tools/install) to be built. This is known for at least the x86\_64-darwin architecture.

## Wildcards for testing mutants [¶](https://mutmut.readthedocs.io/en/latest/\#wildcards-for-testing-mutants "Link to this heading")

Unix filename pattern matching style on mutants is supported. Example:

```
mutmut run "my_module*"
mutmut run "my_module.my_function*"
```

In the browse TUI you can press f to retest a function, and m to retest
an entire module.

## Configuration [¶](https://mutmut.readthedocs.io/en/latest/\#configuration "Link to this heading")

In setup.cfg in the root of your project you can configure mutmut if you need to:

```
[mutmut]
source_paths=src/
pytest_add_cli_args_test_selection=tests/
```

If you use pyproject.toml, you must specify the paths as array in a tool.mutmut section:

```
[tool.mutmut]
source_paths = [ "src/" ]
pytest_add_cli_args_test_selection= [ "tests/" ]
```

See below for more options for configuring mutmut.

### “also copy” files [¶](https://mutmut.readthedocs.io/en/latest/\#also-copy-files "Link to this heading")

To run the full test suite some files are often needed above the tests and the
source. You can configure to copy extra files that you need by adding
directories and files to also\_copy in your setup.cfg:

```
also_copy=
    iommi/snapshots/
    conftest.py
```

### Limit stack depth [¶](https://mutmut.readthedocs.io/en/latest/\#limit-stack-depth "Link to this heading")

In big code bases some functions are called incidentally by huge swaths of the
codebase, but you really don’t want tests that hit those executions to count
for mutation testing purposes. Incidentally tested functions lead to slow
mutation testing as hundreds of tests can be checked for things that should
have clean and fast unit tests, and it leads to bad test suites as any
introduced bug in those base functions will lead to many tests that fail which
are hard to understand how they relate to the function with the change.

You can configure mutmut to only count a test as being relevant for a function
if the stack depth is below some limit. Only stack frames from code inside source\_paths
is counted towards the limit, 3rd party libraries are ignored. In your setup.cfg add:

```
max_stack_depth=8
```

A lower value will increase mutation speed and lead to more localized tests,
but will also lead to more surviving mutants that would otherwise have been
caught.

### Exclude files from mutation [¶](https://mutmut.readthedocs.io/en/latest/\#exclude-files-from-mutation "Link to this heading")

By default mutmut mutates all python files in source\_paths.
You can exclude files from mutation in setup.cfg:

```
only_mutate=
    src/api/*
    src/services/*
do_not_mutate=
    *__tests.py
```

### Enable coverage.py filtering of lines to mutate [¶](https://mutmut.readthedocs.io/en/latest/\#enable-coverage-py-filtering-of-lines-to-mutate "Link to this heading")

By default, mutmut will mutate only functions that are called. But, if you would like a finer grained (line-level)
check for coverage, mutmut can use coverage.py to do that.

If you only want to mutate lines that are called (according to coverage.py), you can set
mutate\_only\_covered\_lines to true in your configuration. The default value is false.

```
mutate_only_covered_lines=true
```

This also honours the lines coverage.py is told to leave out of its measurement, so code
marked with \# pragma: no cover, or matched by your exclude\_lines/exclude\_also
settings, is not mutated either. Such code is not held to your test suite, so mutants
there could only ever show up as survivors.

### Filter generated mutants with type checker [¶](https://mutmut.readthedocs.io/en/latest/\#filter-generated-mutants-with-type-checker "Link to this heading")

When your project is type checked using mypy or pyrefly, you can also use it to filter out invalid mutants.
For instance, mutmut mutates x: str = ‘foo’ to x: str = None which can easily caught by type checkers.

Using this filter can improve performance and reduce noise, however it can also hide a few relevant mutations:

1. x: str = None may not be valid, but if your tests do not detect such a change it indicates that

the value of x is not properly tested (even if your type checker would catch this particular modification)

2. In some edge cases with class properties (usually in the \_\_init\_\_ method), the way mypy and pyrefly infer types does not work well

with the way mutmut mutates code. Some valid mutations like changing self.x = 123 to self.x = None can
be filtered out, even though the may be valid.


To enable this filtering, configure the type\_check\_command to output json results as follows:

```
# for pyrefly
type_check_command = ['pyrefly', 'check', '--output-format=json']
# for mypy
type_check_command = ['mypy', 'your_source_dir', '--output', 'json', '--disable-error-code', 'unused-ignore']
```

Currently, only pyrefly and mypy are supported.
With pyright and ty, mutating a class method Foo.bar() can break the types of all methods of Foo,
and therefore mutmut cannot match the type error with the mutant that caused the type error.

### Enable debug output (increase verbosity) [¶](https://mutmut.readthedocs.io/en/latest/\#enable-debug-output-increase-verbosity "Link to this heading")

By default, mutmut “swallows” all the test output etc. so that you get a nice clean output.

If you want to see all the detail to aid with debugging, you can set debug to true in your configuration.
Note that not all displayed errors are necessarily bad. In particular test runs of the mutated code will lead
to failing tests.

```
debug=true
```

### Disable setproctitle (macOS) [¶](https://mutmut.readthedocs.io/en/latest/\#disable-setproctitle-macos "Link to this heading")

Mutmut uses `setproctitle` to show the current mutant name in the process
list, which is helpful for monitoring long runs. However, `setproctitle`
uses CoreFoundation APIs on macOS that are not fork-safe, causing segfaults
in child processes.

By default, mutmut automatically disables `setproctitle` on macOS and
enables it on other platforms. If you need to override this (e.g. to enable it on
macOS at your own risk, or to disable it on other platforms), set `use_setproctitle`:

```
# pyproject.toml
[tool.mutmut]
use_setproctitle = false
```

### Disabling mutation on specific code [¶](https://mutmut.readthedocs.io/en/latest/\#disabling-mutation-on-specific-code "Link to this heading")

If you do not want to mutate specific parts of your code, you can disable mutation
via the following options. Examples where this could be relevant:

- If you don’t want to test exact log messages (e.g. logging.info(“Foo”) versus the mutated logging.info(“XXFooXX”))

- Optimizing break instead of continue. The code runs fine when mutating break
to continue, but it’s slower.


#### Skipping via regex [¶](https://mutmut.readthedocs.io/en/latest/\#skipping-via-regex "Link to this heading")

You can use a regex on the source code, to tell mutmut which expressions it should not mutate:

```
# pyproject.toml
[tool.mutmut]
do_not_mutate_patterns = [\
    # disable mutations of all logger.info/debug/... statements\
    'logger\.\w+',\
    # disable mutating exceptions\
    'raise \w+',\
]
```

Mutmut will match the regex on the source code
and skip mutating any expression on the matched lines.

#### Skipping via code comments [¶](https://mutmut.readthedocs.io/en/latest/\#skipping-via-code-comments "Link to this heading")

You can use following comments to disable mutation:

- \# pragma: no mutate: disable mutation on this single line

- \# pragma: no mutate block: disable mutation in the whole intendation block

- \# pragma: no mutate start/end: disable mutation between the start and end comments


#### Skipping single lines [¶](https://mutmut.readthedocs.io/en/latest/\#skipping-single-lines "Link to this heading")

You can mark lines like this to stop mutating them:

```
some_code_here()  # pragma: no mutate
```

#### Skipping Code Blocks [¶](https://mutmut.readthedocs.io/en/latest/\#skipping-code-blocks "Link to this heading")

You can skip an entire indentation block from mutation using
`# pragma: no mutate block`. This works on any compound statement –
functions, classes, `if`/`elif`/`else`, loops, context managers, etc.

Both syntax styles are supported:

- `# pragma: no mutate block`

- `# pragma: no mutate: block`


**Skipping an entire function or class** – place the pragma inline on the
definition line. The entire node (including all children) is skipped and no
trampoline is generated:

```
def complex_algorithm():  # pragma: no mutate block
    return some_complex_calculation()

class MySettings:  # pragma: no mutate block
    DEBUG = True
    MAX_RETRIES = 3
```

**Skipping only the body of a function** – place the pragma on its own line
inside the function. The function definition (including default arguments) is
still mutable, but the body is suppressed:

```
def foo(val=1):
    # pragma: no mutate block
    x = 1
    y = complex_calculation()
    z = x + y

def bar():
    # this function is still mutated normally
    return 42
```

**Skipping an \`\`if\`\` branch without affecting \`\`elif\`\`/\`\`else\`\`** – place
the pragma inline on the `if`. Only the `if` condition and its indented
body are suppressed; sibling branches (`elif`, `else`) remain mutable
because they exit the original indentation scope:

```
if error_condition:  # pragma: no mutate block
    log_error()
    send_alert()
elif other_condition:
    # still mutated -- this branch is outside the block scope
    handle_other()
else:
    # still mutated
    handle_success()
```

The same principle applies to `for`/`else`, `while`/`else`,
`try`/`except`/`finally`, and `match`/`case`.

This is useful for:

- Functions or classes that should be excluded from mutation entirely

- Error-handling branches that are hard to unit test in isolation

- Logging or telemetry blocks that don’t affect program correctness

- Generated or boilerplate code within an otherwise mutable function


#### Skipping Code Regions [¶](https://mutmut.readthedocs.io/en/latest/\#skipping-code-regions "Link to this heading")

For suppressing mutations across a range of lines regardless of indentation,
use `# pragma: no mutate start` and `# pragma: no mutate end`:

```
a = mutate_this()

# pragma: no mutate start
b = skip_this()
c = skip_this_too()
# pragma: no mutate end

d = mutate_this_too()
```

Every line between the markers (inclusive) is suppressed. This works inside
functions, classes, or at module level, and ignores indentation entirely.

An unmatched `# pragma: no mutate end` without a preceding `start` raises
a `PragmaParseError` at parse time. An unclosed `# pragma: no mutate start`
(no matching `end` before end-of-file) raises a `PragmaParseError`.
Both errors include the filename and line number.

**Nesting restriction:** opening a new `block` or `start` context while
another context is already active is not allowed and raises a
`PragmaParseError`. The error message includes both the offending line and
the line where the existing context was opened. Close the current context
first (dedent for `block`, or `# pragma: no mutate end` for `start`)
before opening a new one.

### Modifying pytest arguments [¶](https://mutmut.readthedocs.io/en/latest/\#modifying-pytest-arguments "Link to this heading")

You can add and override pytest arguments:

```
# for CLI args that select or deselect tests, use `pytest_add_cli_args_test_selection`
pytest_add_cli_args_test_selection = ["-m", "not fail", "-k", "test_include"]

# for other CLI args, use `pytest_add_cli_args`
pytest_add_cli_args = ["-p", "no:some_plugin"] # disable a plugin
pytest_add_cli_args = ["-o", "xfail_strict=False"] # overrides xfail_strict from your normal config

# if you want to ignore the normal pytest configuration
# you can specify a diferent pytest ini file to be used
pytest_add_cli_args = ["-c", "mutmut_pytest.ini"]
also_copy = ["mutmut_pytest.ini"]
```

### Detecting dependency and config changes [¶](https://mutmut.readthedocs.io/en/latest/\#detecting-dependency-and-config-changes "Link to this heading")

Between runs, mutmut only re-tests mutants in functions whose source changed.
Changes outside your Python source — a dependency upgrade, a data file, a
config file — cannot be tied to a function, so they would otherwise be missed
and you would get cached results that no longer reflect reality.

To catch this, mutmut detects non-Python files that changed since the last full
run and warns you about them. If your project is a git repository and git is
installed, mutmut uses git (a soft dependency no extra package is required) to
find every changed non-Python file, respecting your .gitignore. Python files
are excluded because their changes are already tracked per function.

On a full run with git available, mutmut also records the content hashes of the
tracked non-Python files. This means a later run in an environment without git
(for example a different CI stage) can still detect changes to that known set of
files, even though it cannot discover brand-new ones.

When git is unavailable, mutmut falls back to hashing a curated set of build and
dependency files:

- pyproject.toml

- setup.cfg

- setup.py

- requirements\*.txt

- poetry.lock

- uv.lock

- Pipfile

- Pipfile.lock


You can watch additional files (for example data files your tests depend on)
with the cache\_invalidation\_files config, which accepts glob patterns
resolved against the project root. These are checked even when git ignores them,
and are never dropped by the exclusions below:

```
cache_invalidation_files = [ "queries/*.sql", "config/*.yaml" ]
```

Git detection reports every changed non-Python file, so mutmut drops files that
practically never affect tests (markdown, LICENSE, CHANGELOG, docs/, git
and editor metadata, …). Exclude additional noisy files with
cache\_invalidation\_exclude (glob patterns, \* spans directories):

```
cache_invalidation_exclude = [ "*.json", "fixtures/snapshots/*" ]
```

When a watched file changes, on\_dependency\_change controls what happens:

- warn (default): list the changed files and keep the cache.

- rerun: re-test all mutants.

- ignore: do nothing.


```
on_dependency_change = "warn"
```

Git detection is on by default; disable it (forcing the curated-list fallback)
with:

```
use_git_change_detection = false
```

Changes to mutmut’s own result-affecting config (such as pytest\_add\_cli\_args,
type\_check\_command, or the timeout settings) are always detected and
invalidate the affected cached results automatically.

### Unstable configs [¶](https://mutmut.readthedocs.io/en/latest/\#unstable-configs "Link to this heading")

Following configurations exist, but may be changed in any minor version.
If you use them, expect that a new version could change or break this feature.

```
# Configure how long mutmut waits before killing a slow mutation
# Currently calculated as (duration_of_original_tests + timeout_constant) * timeout_multiplier seconds
timeout_constant = 1.0
timeout_multiplier = 15.0
```

## Example mutations [¶](https://mutmut.readthedocs.io/en/latest/\#example-mutations "Link to this heading")

- Integer literals are changed by adding 1. So 0 becomes 1, 5 becomes 6, etc.

- < is changed to <=

- break is changed to continue and vice versa


In general the idea is that the mutations should be as subtle as possible.
See node\_mutation.py for the full list and test\_mutation.py for tests describing them.

## Workflow [¶](https://mutmut.readthedocs.io/en/latest/\#workflow "Link to this heading")

This section describes how to work with mutmut to enhance your test suite.

1. Run mutmut with mutmut run. A full run is preferred but if you’re just
getting started you can exit in the middle and start working with what you
have found so far.

2. Show the mutants with mutmut browse

3. Find a mutant you want to work on and write a test to try to kill it.

4. Press r to rerun the mutant and see if you successfully managed to kill it.


Mutmut keeps the data of what it has done and the mutants in the mutants/
directory. If you want to make sure you run a full mutmut run you can delete
this directory to start from scratch.

## Mutation score badges [¶](https://mutmut.readthedocs.io/en/latest/\#mutation-score-badges "Link to this heading")

mutmut badge turns mutmut export-cicd-stats output into [Shields endpoint JSON](https://shields.io/badges/endpoint-badge):

```
mutmut export-cicd-stats
mutmut badge --output mutation-score.json
```

```
![mutation](https://img.shields.io/endpoint?url=https://example.com/mutation-score.json)
```

## Contributing to Mutmut [¶](https://mutmut.readthedocs.io/en/latest/\#contributing-to-mutmut "Link to this heading")

If you wish to contribute to Mutmut, please see our [contributing guide](https://mutmut.readthedocs.io/en/latest/CONTRIBUTING.rst).

## Resources [¶](https://mutmut.readthedocs.io/en/latest/\#resources "Link to this heading")

- [Source Code on Github](https://github.com/boxed/mutmut)

- [Travis Testing](https://travis-ci.org/boxed/mutmut)

- [Python Package Index](http://pypi.org/pypi/mutmut/)


[mutmut](https://mutmut.readthedocs.io/en/latest/#)

### [Table of Contents](https://mutmut.readthedocs.io/en/latest/\#)

- [mutmut - python mutation tester](https://mutmut.readthedocs.io/en/latest/#)
  - [Requirements](https://mutmut.readthedocs.io/en/latest/#requirements)
  - [Install and run](https://mutmut.readthedocs.io/en/latest/#install-and-run)
  - [Wildcards for testing mutants](https://mutmut.readthedocs.io/en/latest/#wildcards-for-testing-mutants)
  - [Configuration](https://mutmut.readthedocs.io/en/latest/#configuration)
    - [“also copy” files](https://mutmut.readthedocs.io/en/latest/#also-copy-files)
    - [Limit stack depth](https://mutmut.readthedocs.io/en/latest/#limit-stack-depth)
    - [Exclude files from mutation](https://mutmut.readthedocs.io/en/latest/#exclude-files-from-mutation)
    - [Enable coverage.py filtering of lines to mutate](https://mutmut.readthedocs.io/en/latest/#enable-coverage-py-filtering-of-lines-to-mutate)
    - [Filter generated mutants with type checker](https://mutmut.readthedocs.io/en/latest/#filter-generated-mutants-with-type-checker)
    - [Enable debug output (increase verbosity)](https://mutmut.readthedocs.io/en/latest/#enable-debug-output-increase-verbosity)
    - [Disable setproctitle (macOS)](https://mutmut.readthedocs.io/en/latest/#disable-setproctitle-macos)
    - [Disabling mutation on specific code](https://mutmut.readthedocs.io/en/latest/#disabling-mutation-on-specific-code)
      - [Skipping via regex](https://mutmut.readthedocs.io/en/latest/#skipping-via-regex)
      - [Skipping via code comments](https://mutmut.readthedocs.io/en/latest/#skipping-via-code-comments)
      - [Skipping single lines](https://mutmut.readthedocs.io/en/latest/#skipping-single-lines)
      - [Skipping Code Blocks](https://mutmut.readthedocs.io/en/latest/#skipping-code-blocks)
      - [Skipping Code Regions](https://mutmut.readthedocs.io/en/latest/#skipping-code-regions)
    - [Modifying pytest arguments](https://mutmut.readthedocs.io/en/latest/#modifying-pytest-arguments)
    - [Detecting dependency and config changes](https://mutmut.readthedocs.io/en/latest/#detecting-dependency-and-config-changes)
    - [Unstable configs](https://mutmut.readthedocs.io/en/latest/#unstable-configs)
  - [Example mutations](https://mutmut.readthedocs.io/en/latest/#example-mutations)
  - [Workflow](https://mutmut.readthedocs.io/en/latest/#workflow)
  - [Mutation score badges](https://mutmut.readthedocs.io/en/latest/#mutation-score-badges)
  - [Contributing to Mutmut](https://mutmut.readthedocs.io/en/latest/#contributing-to-mutmut)
  - [Resources](https://mutmut.readthedocs.io/en/latest/#resources)

### Github

Star boxed/mutmut on GitHub[Star](https://github.com/boxed/mutmut) [1,407](https://github.com/boxed/mutmut/stargazers)

### Quick search

Versions**[latest](https://mutmut.readthedocs.io/en/latest/)**On Read the Docs[Project Home](https://app.readthedocs.org/projects/mutmut/?utm_source=mutmut&utm_content=flyout)[Builds](https://app.readthedocs.org/projects/mutmut/builds/?utm_source=mutmut&utm_content=flyout)Search

* * *

[Addons documentation](https://docs.readthedocs.io/page/addons.html?utm_source=mutmut&utm_content=flyout) ― Hosted by
[Read the Docs](https://about.readthedocs.com/?utm_source=mutmut&utm_content=flyout)

---

## Agent Navigation

### Machine endpoints
- Knowledge graph: https://pyweb.dev/api/graph.json
- Graph analysis: https://pyweb.dev/api/graph-analysis.json
- Context index: https://pyweb.dev/llms.txt
