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.
- 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.