37 / 62 · 22 Test Strategy & Quality Metrics · Measuring Test Effectiveness← prev⊞ allnext →☰ Read as one page
5.3Mutation Testing: The True Measure of Test Quality
What Is Mutation Testing?
Mutation testing measures test effectiveness by deliberately introducing bugs (mutations) into your code and checking whether your tests catch them.
Original code: if (age >= 18) return "adult";
Mutation 1: if (age > 18) return "adult"; (changed >= to >)
Mutation 2: if (age >= 17) return "adult"; (changed 18 to 17)
Mutation 3: if (age >= 18) return "child"; (changed return value)
Mutation 4: if (age <= 18) return "adult"; (changed >= to <=)
If your tests catch (kill) all four mutations, your tests are effective for this code. If any mutation survives (tests still pass), your tests have a gap.
Mutation Score
Formula:
Mutation Score = (Killed Mutants / Total Mutants) x 100
Interpretation:
| Score | Interpretation |
|---|---|
| > 90% | Excellent. Tests thoroughly verify behavior, not just execution. |
| 70-90% | Good. Some gaps exist but major behaviors are covered. |
| 50-70% | Moderate. Tests are missing significant verification. |
| < 50% | Poor. Tests run the code but barely verify it. |
Common Mutation Operators
| Operator | What It Does | Example |
|---|---|---|
| Arithmetic | Changes +, -, *, / | a + b becomes a - b |
| Relational | Changes <, >, <=, >=, ==, != | x >= 10 becomes x > 10 |
| Boolean | Changes &&, ||, ! | a && b becomes a || b |
| Return value | Changes return values | return true becomes return false |
| Void method | Removes method calls | sendEmail() becomes // removed |
| Constant | Changes constant values | MAX_RETRY = 3 becomes MAX_RETRY = 0 |
Mutation Testing Tools
| Language | Tool | Notes |
|---|---|---|
| JavaScript/TypeScript | Stryker | Most mature JS mutation testing tool |
| Java | PIT (Pitest) | Industry standard for Java |
| Python | mutmut | Lightweight, easy to integrate |
| C# | Stryker.NET | .NET port of Stryker |
| Go | go-mutesting | Still evolving |
Practical Considerations
- Mutation testing is slow. It runs your test suite once per mutation. A suite with 100 tests and 500 mutations means 50,000 test executions.
- Run it on critical modules only. Do not mutation-test your entire codebase. Focus on the highest-risk areas.
- Combine with coverage. Use code coverage to find untested code. Use mutation testing to verify that tested code is actually being tested effectively.