Mutation Testing Analysis
Purpose
Run mutation testing on a target module, analyze surviving mutants, and produce actionable recommendations to strengthen the test suite.
When to Use
- •After achieving high line/branch coverage but suspecting tests lack assertion depth
- •During code review to evaluate test quality for critical modules
- •Before releasing financial, security, or safety-critical code
- •When setting up mutation testing for the first time in a project
Steps
Step 1: Identify the Target
Determine:
- •Which source files to mutate (focus on critical business logic first)
- •Which test files exercise those source files
- •The language and appropriate mutation tool:
- •TypeScript/JavaScript: Stryker Mutator
- •Python: mutmut
- •Go: go-mutesting or gremlins
Step 2: Configure the Mutation Tool
Stryker (TypeScript/JavaScript):
json
{
"mutate": ["src/services/transfer.ts"],
"testRunner": "jest",
"reporters": ["html", "clear-text"],
"coverageAnalysis": "perTest",
"thresholds": { "high": 80, "low": 60, "break": 50 }
}
mutmut (Python):
ini
[tool.mutmut] paths_to_mutate = "src/services/" tests_dir = "tests/" runner = "python -m pytest -x"
Go:
bash
go-mutesting ./internal/service/...
Step 3: Run Mutation Testing
Execute the tool and capture the report. Note:
- •Total mutants generated
- •Mutants killed (tests detected the change)
- •Mutants survived (tests missed the change)
- •Equivalent mutants (change doesn't affect behavior)
- •Timeouts (infinite loops from mutation)
Step 4: Classify Surviving Mutants
For each surviving mutant, classify it into one of these categories:
| Category | Description | Action |
|---|---|---|
| Missing test | No test covers this code path | Write a new test targeting this path |
| Weak assertion | Test runs the code but doesn't assert the result | Add or strengthen assertions |
| Equivalent mutant | The mutation doesn't change observable behavior | Mark as ignored in config |
| Dead code | The mutated code is unreachable | Remove the dead code |
| Test gap | Test exists but doesn't exercise the specific branch | Add targeted test case |
Step 5: Prioritize Fixes
Sort surviving mutants by impact:
- •Critical path mutants (payment, auth, transfers) -- fix immediately
- •Business logic mutants (order processing, validation) -- fix in current sprint
- •Utility mutants (formatting, logging) -- fix when convenient
- •Equivalent mutants -- document and exclude from future runs
Step 6: Write Strengthening Tests
For each fixable surviving mutant:
- •Identify the exact mutation (e.g.,
>changed to>=) - •Write a test that would fail if that mutation were applied
- •Verify the test passes against the original code
- •Verify the test fails against the mutated code (if tool supports re-run)
Step 7: Re-Run and Verify
After adding tests:
- •Re-run mutation testing on the same module
- •Verify the mutation score improved
- •Check that no new surviving mutants appeared
- •Update thresholds if the module now exceeds the previous target
Step 8: Document and Report
Produce a summary:
- •Module: path to source files
- •Before score: X% (Y killed / Z total)
- •After score: X% (Y killed / Z total)
- •Surviving mutants: categorized list with actions taken
- •Excluded: equivalent mutants with justification
- •Recommendation: target score for this module going forward
Score Thresholds
| Module Type | Target Score | Rationale |
|---|---|---|
| Financial operations | 85%+ | Incorrect behavior = financial loss |
| Authentication/authorization | 85%+ | Incorrect behavior = security breach |
| Business logic | 75%+ | Core application correctness |
| API handlers | 70%+ | Input validation and error handling |
| Utilities | 65%+ | Lower risk, higher cost to achieve |
| Infrastructure/config | 50%+ | Mostly wiring, fewer meaningful mutants |
Output Format
Produce:
- •Mutation testing report summary (scores, surviving mutant count)
- •Categorized list of surviving mutants with recommended actions
- •New or strengthened test files
- •Updated mutation tool configuration (exclusions for equivalent mutants)