Introduction
You can have 100% code coverage but still have bad tests. Mutation testing verifies test quality by introducing small changes (mutations) to your code and checking if tests catch them.
How Mutation Testing Works
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Mutation Testing Process โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Original Code: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ function add(a, b) { โ โ
โ โ return a + b; โ โ
โ โ } โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ Mutation: Change + to - โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ function add(a, b) { โ โ
โ โ return a - b; โ MUTANT โ โ
โ โ } โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ Run Tests: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ add(2, 2) should return 4 โ โ
โ โ โ โ
โ โ Mutant survives? โ (test failed) โ โ
โ โ Tests caught it! โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Mutation Score = Caught / Total Mutants โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
JavaScript/TypeScript: Stryker
Setup
npm install -D @stryker-mutator/core
npx stryker init
Configuration
// stryker.conf.json
{
"$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
"mutator": "typescript",
"packageManager": "npm",
"reporters": ["html", "clear-text", "dashboard"],
"buildCommand": "npm run build",
"testRunner": "command",
"commandRunner": {
"command": "npm test"
},
"mutate": [
"src/**/*.ts",
"!src/**/*.spec.ts"
],
"thresholds": {
"high": 80,
"low": 70,
"break": 60
}
}
Running
# Run mutation testing
npx stryker run
# Output:
# Mutant killed: 45/50 (90%)
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# โ File โ Mutation score โ
# โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
# โ math.ts โ 95% โ
# โ string.ts โ 85% โ
# โ utils.ts โ 75% โ ๏ธ โ
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Java: PIT
Setup
<!-- pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.15.0</version>
</plugin>
</plugins>
</build>
Running
mvn org.pitest:pitest-maven:mutationCoverage
Results
================================================================================
>> mutation coverage report
================================================================================
Classes : 85% (34/40)
Methods : 80% (120/150)
Mutations: 75% (150/200)
UNCOVERED MUTATIONS:
================================================================================
com.example.Utils.java:
Line 45: replaced + with - SURVIVED
Line 67: removed conditional - omitted < SURVIVED
What Gets Mutated
mutations:
- "Arithmetic: + โ -, * โ /, etc."
- "Boolean: true โ false, && โ ||"
- "Comparison: == โ !=, < โ >="
- "Conditional: removed if/else branches"
- "String: '' โ '', .length โ 0"
- "Array: [0] โ arr.first()"
Interpretation
# Mutation score interpretation
scores:
above_80:
label: "Excellent"
description: "Tests are catching most bugs"
70_80:
label: "Good"
description: "Room for improvement"
60_70:
label: "Warning"
description: "Some bugs may slip through"
below_60:
label: "Critical"
description: "Tests not effective"
Key Takeaways
- Mutation testing - Verifies test quality, not just coverage
- Stryker - JavaScript/TypeScript
- PIT - Java
- Goal - Catch as many mutants as possible
Comments