Fast-first mutation testing for pytest. Let the gremlins loose, see which ones survive.
Project description
pytest-gremlins
Fast-first mutation testing for pytest. Speed that makes mutation testing practical for everyday TDD.
Let the gremlins loose. See which ones survive.
Key Features
- Speed-First Architecture - Mutation switching eliminates file I/O and module reloads. Run gremlins in seconds, not hours.
- Native pytest Integration - Zero configuration to start. Just add
--gremlinsto your pytest command. - Coverage-Guided Selection - Only runs tests that actually cover the mutated code. 10-100x fewer test executions in well-modularized codebases.
- Incremental Caching - Results cached by content hash. Unchanged code skips re-testing entirely.
- Parallel Execution - Distribute gremlins across CPU cores for linear speedup.
Quick Start
# Install
pip install pytest-gremlins
# Run mutation testing
pytest --gremlins
That's it. pytest-gremlins will instrument your code, release the gremlins, and report which ones your tests zapped (good!) and which survived (test gaps!).
Why pytest-gremlins?
Code coverage lies. It tells you what code your tests execute, but not whether your tests would catch bugs.
Mutation testing answers a harder question: If I introduce a bug, will my tests fail?
The Problem with Existing Tools
| Tool | Limitation |
|---|---|
| mutmut | Single-threaded by default, no incremental analysis |
| Cosmic Ray | Complex setup; distributed mode requires Celery |
| MutPy | Unmaintained (last update 2019), Python 3.4-3.7 only |
| mutatest | Unmaintained (last update 2022) |
Our Solution: Speed Through Architecture
pytest-gremlins is fast because of how it works, not just parallelization:
- Mutation Switching - Instrument once, toggle mutations via environment variable
- Coverage Guidance - Only run tests that cover the mutated code
- Incremental Analysis - Skip unchanged code on repeat runs
- Parallel Execution - Safe parallelization with no shared state
Performance
Benchmarked against mutmut on a synthetic project:
| Mode | Time | vs mutmut | Speedup |
|---|---|---|---|
--gremlins (sequential) |
17.79s | 14.90s | 0.84x (see note) |
--gremlins --gremlin-parallel |
3.99s | 14.90s | 3.73x faster |
--gremlins --gremlin-parallel --gremlin-cache |
1.08s | 14.90s | 13.82x faster |
Key findings:
- Sequential mode is slower due to subprocess isolation overhead; detailed profiling shows 1.7x slower on small targets
- Parallel mode delivers 3.73x speedup over mutmut
- With caching, subsequent runs are 13.82x faster
- pytest-gremlins found 117 mutations vs mutmut's 86, with 98% kill rate vs 86%
Example Output
================== pytest-gremlins mutation report ==================
Zapped: 142 gremlins (85%)
Survived: 18 gremlins (11%)
Timeout: 5 gremlins (3%)
Error: 2 gremlins (1%)
Top surviving gremlins:
src/auth.py:42 >= -> > (comparison)
src/utils.py:17 + -> - (arithmetic)
src/api.py:88 True -> False (boolean)
Run with --gremlin-report=html for detailed report.
=====================================================================
Timeout and Error categories are only shown when their count is greater than zero.
Installation
# With pip
pip install pytest-gremlins
# With uv
uv add pytest-gremlins
# With poetry
poetry add pytest-gremlins
Requires Python 3.11+
Configuration
Zero configuration required for most projects. The plugin auto-discovers source paths from your
pyproject.toml setuptools config (e.g., [tool.setuptools.packages.find]). If auto-discovery
doesn't find your code, configure paths explicitly:
[tool.pytest-gremlins]
# Operators to use (default: all)
operators = ["comparison", "arithmetic", "boolean"]
# Paths to mutate (optional -- auto-discovered from setuptools metadata)
paths = ["src"]
# Patterns to exclude
exclude = ["**/migrations/*", "**/test_*"]
# Minimum mutation score to pass
min_score = 80
The Gremlins Theme
We use Gremlins movie references as our domain language:
| Traditional Term | Gremlin Term | Meaning |
|---|---|---|
| Original code | Mogwai | Your clean, untouched source code |
| Start mutation testing | Feed after midnight | Begin the mutation process |
| Mutant | Gremlin | A mutation injected into your code |
| Kill mutant | Zap | Your test caught the mutation |
| Surviving mutant | Survivor | Mutation your tests missed |
Documentation
Full documentation: pytest-gremlins.readthedocs.io
Related Projects
- pytest-test-categories - Enforce Google test size standards in Python
- dioxide - Rust-backed dependency injection for Python
Contributing
Contributions welcome! See our Contributing Guide.
This project uses strict TDD discipline with BDD/Gherkin scenarios. All contributions must include tests written before implementation.
Note on code coverage: We target 69% coverage due to inherent limitations in measuring pytest plugins (import timing, subprocess execution). See CONTRIBUTING.md for details.
License
MIT License. See LICENSE.
Changelog
See CHANGELOG.md for release history.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_gremlins-1.5.0b1.tar.gz.
File metadata
- Download URL: pytest_gremlins-1.5.0b1.tar.gz
- Upload date:
- Size: 3.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3e3e8565fdcc710fabe2e4361df8efb04f2e1e04d214a4749fca14a687271d5
|
|
| MD5 |
552455bd2bf67d501b4cd79539aaf72a
|
|
| BLAKE2b-256 |
98c862735d318278adcfd207f2222210038e2f332c4a3253c57e761ca6080d2c
|
Provenance
The following attestation bundles were made for pytest_gremlins-1.5.0b1.tar.gz:
Publisher:
release.yml on mikelane/pytest-gremlins
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_gremlins-1.5.0b1.tar.gz -
Subject digest:
a3e3e8565fdcc710fabe2e4361df8efb04f2e1e04d214a4749fca14a687271d5 - Sigstore transparency entry: 1057073392
- Sigstore integration time:
-
Permalink:
mikelane/pytest-gremlins@e000152ec4059cd955d24502ac09e1fc71e34d72 -
Branch / Tag:
refs/tags/v1.5.0b1 - Owner: https://github.com/mikelane
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e000152ec4059cd955d24502ac09e1fc71e34d72 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pytest_gremlins-1.5.0b1-py3-none-any.whl.
File metadata
- Download URL: pytest_gremlins-1.5.0b1-py3-none-any.whl
- Upload date:
- Size: 93.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9cd9a3f5a620349aa30090c4743963ba251b7a3fefca81210ec25baa66d0abe
|
|
| MD5 |
ad424a2a7ae17c48df223d46475418ae
|
|
| BLAKE2b-256 |
b9d8745da8263afe0233c3db50010dd61b2d8bac5a0e9dd77b07fed30aecaaff
|
Provenance
The following attestation bundles were made for pytest_gremlins-1.5.0b1-py3-none-any.whl:
Publisher:
release.yml on mikelane/pytest-gremlins
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_gremlins-1.5.0b1-py3-none-any.whl -
Subject digest:
f9cd9a3f5a620349aa30090c4743963ba251b7a3fefca81210ec25baa66d0abe - Sigstore transparency entry: 1057073394
- Sigstore integration time:
-
Permalink:
mikelane/pytest-gremlins@e000152ec4059cd955d24502ac09e1fc71e34d72 -
Branch / Tag:
refs/tags/v1.5.0b1 - Owner: https://github.com/mikelane
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e000152ec4059cd955d24502ac09e1fc71e34d72 -
Trigger Event:
push
-
Statement type: