# Mutation Testing

> Measure real test quality by mutating source code and verifying that tests catch the changes. Use after significant Green phases, before /review, and when coverage looks complete but confidence is low.

- Skill: `volodymyrkoval/mutation-testing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add volodymyrkoval/mutation-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/volodymyrkoval/mutation-testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: volodymyrkoval (https://skillmd.com/u/volodymyrkoval)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/volodymyrkoval/mutation-testing

---


# Mutation Testing

Coverage tells you the line executed. Mutation testing tells you the line was *tested*.

## When to run

✅ End of a plan, before merging
✅ When a reviewer suspects test theater (high coverage, low confidence)
✅ Before security-critical code ships
✅ After a refactor — surviving mutants reveal behavior tests didn't pin

❌ Mid-cycle (too slow — breaks the inner TDD loop)
❌ On trivial diffs (rename, typo, import reorder)

## How to run

Use `/mutate`. It detects the stack and runs on the diff by default.

| Stack | Tool | Invocation |
|-------|------|------------|
| TS/JS with Stryker | Stryker | `npx stryker run --incremental --mutate "<changed files>"` |
| Python with mutmut | mutmut | `mutmut run --paths-to-mutate "<changed files>"` |

If neither tool is installed, follow `QUALITY_TOOLING.md` or run `/bootstrap` on a fresh project.

## Reading the output

Each mutant is a deliberate code change (`<` → `<=`, `true` → `false`, delete a statement).

| Result | Meaning | Action |
|--------|---------|--------|
| **Killed** | A test failed — good | None. Tests did their job. |
| **Survived** | All tests passed despite the mutation — bad | Write a test that *would* have failed. Real gap. |
| **Timeout** | Tests hung (usually infinite loop from the mutation) | Review; may indicate a missing termination guard |
| **No coverage** | Line isn't exercised at all | Add a test, or delete the code |

**Every surviving mutant becomes a `- [ ]` todo** in the active plan (or a follow-up). Write the test that kills it in the next Red phase. Never "fix" by modifying production code to make the mutant die — that removes real behavior.

## Thresholds (gate, not target)

- **≥80% mutation score** — ship
- **60–79%** — acceptable; investigate survivors individually
- **<60%** — tests are insufficient; block merge

Don't chase 100%. Equivalent mutants (semantically identical changes) are unkillable. Focus survivors in business logic.

## Common survivor patterns

| Pattern | What it means | Test to add |
|---------|---------------|-------------|
| Boundary flip (`>` ↔ `>=`) | No test exercises the exact boundary | Input at the boundary value |
| Logical op (`&&` ↔ `\|\|`) | Missing case where only one operand is true | Both one-true-one-false combinations |
| Return value mutated | Return never asserted; only called for side effects | Assert on the return |
| Deleted statement | Statement is dead or untested side effect | Assert on observable consequence |
| `true` ↔ `false` constant | Constant is pass-through; no test checks the flipped path | Case where the flag changes behavior |

Each maps to a specific, narrow test.

## Discipline

- Run mutation testing *between* cycles, never inside one.
- Don't add mutation testing to commit hooks or CI gates that block everyday work — it's too slow. Treat it as a periodic quality sweep.
- A surviving mutant is a test gap, not a code bug. The fix is always a new test.

