rgoussu@goussu: ~/library/rust/testing
~/library/rust/testing cat mutation-testing.md

Mutation testing — cargo-mutants

# Judging the test suite by mutating the code — cargo-mutants' approach, reading missed-mutant reports, scoping and CI economics, and the type-system twist on mutation in Rust.

Conceptsaved 2026-08-09 #rust#testing#mutation#quality

Overview

Mutation testing flips the test question: instead of "does the code pass the tests?", it plants small bugs (mutants) and asks whether the tests notice. cargo-mutants is Rust's working tool — younger than Java's PIT, more active than Go's gremlins — with a design twist suited to Rust: it patches the source, so every mutant must still compile, and the type system silently kills a class of mutants other languages must test for.

Key points

  • How it works: cargo-mutants enumerates mutation sites (replace a function body with Default::default(), swap operators, negate conditions, delete match arms…), applies each in a copy of the tree, and runs the suite. Outcomes: caught (a test failed — good), missed (suite stayed green — the finding), unviable (didn't compile — ignored).
  • Unviable mutants are the Rust twist: many mutations that would be live bugs in Java/Go simply don't type-check here — the compiler is your first mutation killer; what's left missed is therefore concentrated signal.
  • Read misses as "untested behaviour", not "add an assert": a missed body-replacement means no test depends on that function's output — decide whether that's a coverage gap, dead code, or accepted risk; the tool's honesty is the value.
  • Scope it or it eats CI: full runs are suite-runtime × mutant-count. Use --in-diff (mutants only in changed code — the PR-gate mode), -f/--file for the correctness-critical crates, --timeout for infinite-loop mutants, and nextest as the runner (--test-tool nextest).
  • .cargo/mutants.toml holds exclusions (generated code, main.rs, log lines) — curate them; a noisy mutation report gets ignored like any noisy gate.
  • The economics: worth running continuously on parsing/money/consensus-grade logic, occasionally elsewhere; pairs naturally with property-based tests, which tend to kill whole mutant families at once.

Examples

cargo install cargo-mutants
cargo mutants -p core --timeout 60        # one crate, bounded
cargo mutants --in-diff <(git diff main)  # PR mode: only mutate the change
cargo mutants --list                      # preview sites without running

Related