# What Is Mutation Testing? — The Metric That Checks Your Assertions

Canonical: https://commareports.com/glossary/mutation-testing
Published: 2026-09-11

> Mutation testing changes your code on purpose and checks whether any test notices. Mutation score, surviving mutants, equivalent mutants, and why it exposes what coverage cannot.

# Mutation testing

**Mutation testing deliberately introduces small changes — _mutants_ —
into the code under test, then runs the suite against each one. A mutant
that makes a test fail is _killed_; one that leaves the suite green
_survived_, meaning no test actually checks that behaviour.**

It is the direct answer to the weakness of
[code coverage](/glossary/code-coverage): coverage counts execution, and
a test that calls a function and asserts nothing covers it completely.

## What a mutant looks like

| Operator             | Before     | After         |
| -------------------- | ---------- | ------------- |
| Conditional boundary | `a < b`    | `a <= b`      |
| Negate conditional   | `a == b`   | `a != b`      |
| Arithmetic           | `a + b`    | `a - b`       |
| Return value         | `return x` | `return null` |
| Remove call          | `log(x);`  | _(removed)_   |
| Boolean literal      | `true`     | `false`       |

If flipping `<` to `<=` in a billing calculation leaves every test
passing, the boundary is untested — and boundaries are where bugs live.

## Mutation score

```
mutation score = killed / (total − equivalent)
```

Expect it to be well below your coverage percentage. A suite at 90% line
coverage scoring 55% is ordinary, and the 35-point gap is the real
finding: those tests execute the code without checking it.

**Equivalent mutants** are changes that do not alter observable
behaviour, so nothing can kill them — a mutated statement whose result is
discarded, for instance. Detecting them is undecidable in general, which
is why 100% is not a target and why tools report a separate "survived"
versus "no coverage" breakdown.

## The tools

Stryker (JavaScript, TypeScript, C#, Scala), PIT / pitest (JVM),
`mutmut` and Cosmic Ray (Python), `mutant` (Ruby), `go-mutesting`, and
`cargo-mutants` (Rust)
([Stryker reports](/share-stryker-report) ·
[PIT reports](/share-pitest-report)).

## Why it is slow, and how to use it anyway

The suite runs once per mutant, and a medium codebase generates
thousands. Mitigations that make it practical:

- **Coverage-guided selection** — only run the tests that cover the
  mutated line.
- **Incremental mode** — mutate only files changed in the branch.
- **Parallelism**, and a nightly full run rather than per-commit.

The realistic adoption pattern is incremental on pull requests, full on a
schedule ([scheduled HTML reports](/features/routines/scheduled-html-reports)).

## The report is the deliverable

A mutation score is a number with no action attached. The HTML report is
the actionable part: source browsable line by line, each surviving mutant
shown in place with the exact change it made and which tests ran against
it.

That is a conversation, not a metric — "this survivor is equivalent",
"this one is a real gap, I'll add the boundary case". Publishing the
report to a permanent URL lets reviewers put those judgements next to
each survivor, and keeps them readable next run
([comment on HTML](/comment-on-html)).

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited
revision history.

**[Publish a mutation report →](https://commareports.com/)**

### Related

- [Share a Stryker report](/share-stryker-report) · [PIT](/share-pitest-report) · [Coverage](/share-coverage-report)
- [Code coverage](/glossary/code-coverage) · [Flaky test](/glossary/flaky-test)
- [QA engineers](/for/qa-engineers) · [Glossary](/glossary)
