Mutation testing
Skill jacob-balslev/skills/skills/quality-assurance/mutation-testing
Public Agent Skills library exported from skill-graph. Install: npx skills add jacob-balslev/skills
npx -y skills add jacob-balslev/skills --skill mutation-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when reasoning about mutation testing as a behavioral signal of test-suite quality: the mutant-operator vocabulary (replace operator, negate condition, flip Boolean, remove statement, alter constant), the mutation-score metric (killed mutants / total non-equivalent mutants), why mutation testing is a stronger signal than code coverage (coverage measures execution; mutation measures whether the tests would catch a defect), the equivalent-mutant problem (mutants that produce no observable behavior change despite syntactic difference), selective and incremental mutation strategies that make the technique practical for large codebases (PIT, Stryker), and the relationship between mutation testing and TDD. Do NOT use for the structural signal of how much code tests reach (use test-coverage-strategy), the construction of test doubles (use test-doubles-design), the strategic question of what to test at which level (use testing-strategy), or generic fault injection at runtime (use chaos-engineering).
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
28.2 KB, as published. Nobody here has run it
Mutation Testing
Concept of the skill
Mutation testing is the behavioral signal of test-suite quality. The tool automatically modifies the production code by small, syntactically-valid changes called mutants — replace < with <=, negate a condition, flip a Boolean, alter a constant from 42 to 43, delete a statement, swap a return value for null, no-op a void method call — and runs the test suite against each modified version. If the tests fail on a mutant, the mutant is killed — the tests caught the change. If the tests still pass, the mutant survived — the tests do not actually verify the behavior at that code location, even if coverage said they reached it. The mutation score is killed / (total − equivalent mutants), where equivalent mutants are syntactic changes that produce no observable behavior difference (5-15% noise typical).
Replaces coverage-as-quality-signal with a direct behavioral verification signal. Solves the problem that high code coverage is a weak indicator of test effectiveness — Inozemtseva & Holmes (2014) showed coverage is not strongly correlated with test-suite effectiveness, which is why teams routinely ship coverage-90% codebases with bugs slipping through. Coverage asks "did the tests reach this line?"; mutation asks "would the tests catch a defect here?" — the second question is closer to what we care about, and the answer is more specific: each survived mutant is a directly addressable test-suite gap with a known location and a known kind of defect (off-by-one, condition negation, sign error, missing return assertion). Just et al. (2014) validated that mutation score correlates with real fault-detection rate, making it a meaningful proxy where coverage falls short.
Distinct from test-coverage-strategy, which owns the structural signal of which code the test suite reaches — coverage is a necessary precondition for mutation testing to apply (an uncovered mutant trivially survives because no test runs on it); coverage is the floor, mutation is the next layer. The two compose into a mature test-quality strategy: coverage as floor, mutation as verification. Distinct from test-doubles-design, which owns the construction of mocks/stubs/fakes/spies — this skill measures whether tests built with them actually verify behavior. Distinct from testing-strategy, which owns level choices (unit/integration/e2e) — mutation is a measurement applied at any level. Distinct from chaos-engineering, which is runtime fault injection into a deployed system — mutation is build-time source-code mutation. Distinct from fuzz-testing, which varies inputs to find crashes — mutation varies the program to find untested behaviors. Distinct from test-driven-development, which produces tests with high behavioral specificity as a side effect — mutation testing is one way to measure whether that specificity is actually present. Mutation testing is to a test suite what a fire drill is to a building's evacuation plan — you do not measure preparedness by counting how many exits exist (coverage), you measure it by deliberately staging a fire and watching whether anyone notices in time (mutation kill rate). An exit that nobody walks through during the drill is not really an exit, regardless of how prominently it is signposted. The wrong mental model is that the mutation score is a target to engineer toward — a number to push up the way teams push up coverage, with hard merge-gates on it. It is not. The discipline is not in maximizing the score; it is in reading the survived-mutant list. Each survivor is one of four things: (1) a real test gap — the mutant alters observable behavior and no test catches it; action: write the missing test; (2) an equivalent mutant — the syntactic change has no observable effect; action: mark as equivalent and exclude; (3) an intentional non-test — the code is intentionally unverified (defensive check, log message, debug path); action: annotate, consider whether the policy should change; (4) off-scope code — generated, vendor, scaffolding; action: exclude from analysis. Treating the score as a hard merge-gate without classifying survivors produces Goodharted tests engineered to satisfy the metric without verifying meaningful behavior, plus team frustration from equivalent-mutant noise (5-15%) being misread as real defect signal. The survival's kind (which operator caused which survivor) is part of the diagnostic, not just the count.
Coverage
The behavioral test-suite quality measurement that introduces small syntactically-valid modifications (mutants) to production code and checks whether the test suite distinguishes the mutant from the original. Covers the mutant-operator vocabulary (arithmetic, relational, conditional, logical, constant, statement deletion, return value, method call removal), the kill-or-survive primitive, the mutation score metric, the equivalent-mutant problem and detection heuristics, selective mutation (Offutt et al.'s subset), execution strategies (full / incremental / bytecode / distributed), the modern tooling ecosystem (PIT, Stryker, mutmut, etc.), and the strategic distinction between mutation score as a target (anti-pattern) and the survived-mutant list as a to-do (correct use).
Philosophy of the skill
Mutation testing inverts the coverage question. Coverage asks: did the test reach this line? Mutation asks: would the test catch a defect at this line? The second question is closer to what we actually care about, and the answer is more specific: each survived mutant is a directly addressable test-suite gap with a known location and a known kind of defect.
The discipline is not in maximizing the score; it is in reading the survived-mutant list. Each survivor is either a real gap (the test suite does not verify this behavior — write the test), an equivalent mutant (the syntactic change has no observable effect — exclude it), or an intentional non-test (defensive check, log string, debug path — note it as intentional). Working through the list with this classification produces a stronger test suite without engineering tests to satisfy the metric.
Mutation testing's modern feasibility is what makes it strategic. A decade ago the technique was largely impractical for large codebases. Today's tools — PIT's bytecode mutation, Stryker's incremental analysis, distributed execution, test prioritization — run mutation testing in CI in minutes for codebases of hundreds of thousands of lines. The cost barrier that historically pushed teams to coverage as a substitute is largely gone.
Mutation Operator Catalog (Selective Set)
| Operator | Example | Catches |
|---|---|---|
| Conditional Boundary | < → <= | Off-by-one in comparisons |
| Negate Conditionals | == → != | Inverted-condition bugs |
| Math | + → -, * → / | Arithmetic mistakes |
| Increments | i++ → i-- | Loop-direction bugs |
| Invert Negatives | -x → x | Sign errors |
| Return Values | return x → return null | Missing return assertions |
| Void Method Calls | obj.set(x) → (no-op) | Missing-side-effect bugs |
| Empty Returns | replace return with type's empty/default | Caught only if downstream uses the value |
| Constants | 42 → 43, true → false | Magic-number assertions |
The Offutt et al. selective subset (about 5-8 operators) captures most of the signal of the full operator set at a fraction of the cost.
Mutation vs Coverage — The Composition
| Aspect | Coverage | Mutation |
|---|---|---|
| What it measures | Did the tests execute this code? | Would the tests catch a defect here? |
| Signal direction | Floor (uncovered = unverified) | Direct measure of verification |
| Cost | Low — incremental with test execution | Higher — one test run per mutant |
| Goodhart susceptibility | High (easy to game) | Lower (harder to engineer to without writing real tests) |
| Precondition | None | Coverage at the location |
| Diagnostic output | Map of unreached lines | List of survived mutants |
A mature test-quality strategy uses coverage as the floor (reach everything important) and mutation as the verification signal (verify everything reached).
Working With Survived Mutants
A survived mutant is not automatically a test bug. Classify each:
- Real test gap — the mutant alters observable behavior and no test catches it. Action: write the missing test.
- Equivalent mutant — the syntactic change has no observable effect. Action: mark as equivalent; some tools support inline suppression.
- Intentional non-test — the code is intentionally unverified (defensive check, log message, debug path). Action: annotate; consider whether the policy should change.
- Off-scope code — generated code, vendor code, scaffolding. Action: exclude from mutation analysis.
A test suite that addresses the real-test-gap survivors and accepts the rest will see its mutation score climb organically — and, more importantly, its real defect-detection rate.
Incremental CI Integration
The pattern that makes mutation testing practical in continuous integration:
- Compute the set of changed files in the PR.
- Generate mutants only on changed lines (PIT:
--targetTests+ diff-aware mode; Stryker: incremental mode). - Run the affected tests against each mutant.
- Report new survivors on changed code.
- Block (or warn on) PRs that introduce new survivors above threshold.
This scales to large codebases because the work per PR is bounded by the PR's size, not the codebase's size.
Verification
After applying this skill, verify:
- Mutation testing is paired with coverage, not used as a replacement. Coverage measures reach; mutation measures verification.
- The mutation operator set in use is named and documented (full / selective Offutt subset / custom).
- Survived mutants are classified (real gap / equivalent / intentional non-test / off-scope), not treated as a uniform list of bugs.
- Mutation score is read as a list-of-actions summary, not as a target to engineer toward. Hard merge-gates on the score are avoided unless paired with explicit anti-Goodhart policies.
- Equivalent-mutant noise is acknowledged (5-15% typical) and either accepted in the raw score or excluded from a published adjusted score.
- For CI integration, incremental mutation on changed code is used; full mutation runs are scheduled (nightly, weekly) rather than blocking every PR.
- Mutation testing is not applied to dead code, generated code, or off-scope code that produces noise without value.
- The team can name the operator that caused each surviving mutant (off-by-one, condition negation, etc.) — the survival's kind is part of the diagnostic, not just the count.
Do NOT Use When
| Instead of this skill | Use | Why |
|---|---|---|
| Measuring how much of the code the test suite reaches | test-coverage-strategy | coverage measures structural reach; this skill measures behavioral verification |
| Designing test doubles (mocks, stubs, fakes) | test-doubles-design | test-doubles owns stand-in construction; this skill measures whether the resulting tests verify behavior |
| Choosing test levels (unit/integration/e2e) | testing-strategy | testing-strategy owns the strategic level question |
| Injecting failures into a deployed system | chaos-engineering | chaos is runtime fault injection; this skill is build-time source-code mutation |
| Generating input variations to find crashes | fuzz-testing skill | fuzzing varies inputs; this skill varies the program |
| Iterating on LLM behavior via evals | eval-driven-development | eval-driven-development is the LLM analog; mutation testing is for deterministic code |
Key Sources
- DeMillo, R. A., Lipton, R. J., & Sayward, F. G. (1978). "Hints on Test Data Selection: Help for the Practicing Programmer". IEEE Computer, 11(4), 34-41. The foundational paper introducing the mutation-testing concept and the competent-programmer / coupling-effect hypotheses that justify the technique.
- Jia, Y., & Harman, M. (2011). "An Analysis and Survey of the Development of Mutation Testing". IEEE Transactions on Software Engineering, 37(5), 649-678. The canonical comprehensive survey of mutation testing across decades of research.
- Just, R., Jalali, D., Inozemtseva, L., Ernst, M. D., Holmes, R., & Fraser, G. (2014). "Are Mutants a Valid Substitute for Real Faults in Software Testing?". FSE 2014. The empirical study showing mutation score correlates with real fault-detection rate, validating mutation as a meaningful proxy.
- Andrews, J. H., Briand, L. C., & Labiche, Y. (2005). "Is Mutation an Appropriate Tool for Testing Experiments?". ICSE 2005. Earlier empirical study supporting mutation as a valid measure of test-suite effectiveness.
- Offutt, A. J., Lee, A., Rothermel, G., Untch, R. H., & Zapf, C. (1996). "An Experimental Determination of Sufficient Mutant Operators". ACM Transactions on Software Engineering and Methodology, 5(2), 99-118. The selective-mutation paper that established the small operator subset capturing most signal at a fraction of the cost.
- Coles, H. "PIT Mutation Testing — Documentation". The reference for the canonical JVM mutation-testing tool; bytecode mutation, incremental analysis, CI integration.
- Stryker Mutator. "Stryker — Documentation". The reference for the JS/TS/.NET/Scala mutation-testing tool; framework-integrated and source-level.
- Inozemtseva, L., & Holmes, R. (2014). "Coverage Is Not Strongly Correlated with Test Suite Effectiveness". ICSE 2014. Adjacent finding: coverage's weak correlation with effectiveness is part of why mutation matters as a stronger signal.