Mutation testing checks the checker: it seeds tiny synthetic bugs ("mutants") into the code — flipping < to <=, + to -, && to ||, negating a boolean return — then reruns the real test suite against each mutant. A good suite notices the changed behaviour and fails; a weak suite doesn't.
Mutation Score = Killed / (Total − Equivalent) × 100%
This lab models each mutant's fate with two independent probabilities, matching how real mutation-testing frameworks (PIT, Stryker, mutmut) behave:
covered = random() < coverage
detected = covered && random() < strength × operatorDifficulty
kill = detected && !equivalent
- Test coverage — the fraction of mutants that even get exercised by a test run. An uncovered mutant survives automatically, no matter how sharp the assertions are.
- Assertion strength — given a mutant is exercised, the chance an assertion actually notices the difference (weak
assertNotNull style checks miss far more than precise value/state assertions).
- Operator difficulty — boundary mutants (off-by-one,
<↔<=) are the hardest to catch; arithmetic mutants are the easiest. The grid mixes four operator families with different base detection odds, coloured by their left border in the grid view.
- Equivalent mutants — a small, unavoidable share of mutations don't actually change program behaviour (e.g. mutating dead code). No test suite can kill them; real tools exclude them from the score by hand or heuristics.
Real-world relevance: line/branch coverage alone can hit 100% while still missing bugs, because a line can be "executed" without any assertion checking its result. Mutation score is the harder, more honest measure teams use to judge whether their tests actually verify behaviour — not just that the code ran. The bar panel below breaks the same run down per operator family so you can see which mutation kinds your suite is actually weak against; the sparkline tracks how the score moves across successive runs as you retune the sliders.