4.5.16 · D1Software Engineering

Foundations — Mutation testing

2,796 words13 min readBack to topic

This page assumes you know nothing. Before you touch the parent topic (the main note this page prepares you for), you need a small toolbox of ideas. We build each one from the picture up, in the order they depend on each other, so no word appears before it is earned.


0. The absolute starting point: what is a program?

Figure — Mutation testing
Figure 1: A program as a box — an input value enters on the left, an output value leaves on the right.

Why the topic needs this: every idea below — tests, mutants, killing — is about what happens when a specific input travels through this box and what comes out the other side.


1. A test: the tiny experiment

In code you will see it written like this:

assert classify(5) == "pos"

Read it in plain English: "I assert that feeding 5 to classify gives back "pos". If it doesn't, stop everything and complain."

Why the topic needs this: "killed" and "survived" (defined later) are just the words for whether a test screamed or stayed silent after we broke the code.


2. The comparison symbols: >, >=, <, <=, ==

Mutation testing leans hard on tiny differences like > versus >=. You must feel these in your bones.

Figure — Mutation testing
Figure 2: On the number line, n > 0 (lavender, open dot at 0) and n >= 0 (mint, filled dot at 0) differ at exactly one point — the boundary n = 0.

Why the topic needs this: the most common mutation operator swaps one comparison for its neighbour (>>=, <<=). Understanding where they differ tells you which input can catch the swap.


3. The mutation operator: one tiny nudge

"Syntactic" just means a change to the letters/symbols you type — not to the meaning you intended.

Figure — Mutation testing
Figure 3: A mutant is the original program with exactly one gear (operator) swapped — here > becomes >=.

Why the topic needs this: mutants are the "little bug matches" we light under our smoke-alarm tests. No mutant, no mutation testing.


4. Killed vs survived: reading the scream

Now we combine Section 1 (tests scream or stay silent) with Section 3 (a mutant is broken code).

Why the topic needs this: these three outcomes (killed, survived, equivalent) are the raw counts that feed the score.


5. Turning the counts into a score

We just met three kinds of mutant. If you count how many of each you have, you can build a single quality number. Let's name the counts first, then assemble them.

Why the topic needs this: the whole point of mutation testing is to hand you one honest number for test quality — and part of honesty is knowing when the number is undefined.


6. The nearby idea it's often confused with: coverage

Why the topic needs this: mutation testing exists because coverage answers the wrong question. You can't appreciate the "why" without meeting coverage first.


Prerequisite map

Program input to output

Test with an assertion

Pass silent or Fail scream

Comparisons gt gte lt lte eq

Boundary value where they differ

Mutation operator one tiny change

Mutant broken copy

Killed or Survived

Equivalent mutant

Counts M and K

Mutation score K over M minus E

Code coverage ran not checked


Equipment checklist

Test yourself — reveal each answer only after you've said it out loud.

What does a program turn into what?
It turns an input value into an output value, like a box with an arrow in and an arrow out.
What is an assertion, in one sentence?
A line that claims a result must be true, and screams (fails) if it isn't.
What does a single letter like n stand for in a comparison?
A name for one particular number the program is working with right now (usually the input value).
At which single value do n > 0 and n >= 0 disagree?
At exactly — the boundary value; everywhere else they agree.
Which comparison is the mirror image of >=, and how does it treat the boundary?
<= — the mirror image; like >= it includes the boundary value.
What is a mutation operator?
A rule that makes exactly one tiny syntactic change to the code (e.g. >>=).
What is a mutant?
A copy of the program with one mutation operator applied — one deliberate small bug.
When is a mutant "killed"?
When at least one test fails (screams) on it.
When does a mutant "survive", and what does that reveal?
When all tests still pass despite the bug — it reveals a gap in the tests, not the production code.
What is an equivalent mutant?
A change that behaves identically to the original for every input, so no test can ever kill it.
What do the letters , , stand for?
= total mutants, = equivalent (unkillable) mutants, = killed mutants.
Why is the mutation score and not ?
Because the equivalent mutants can never be killed, so only are fair opportunities.
What happens to the score when ?
It is undefined — dividing by zero — meaning no killable mutants were generated, so there is simply no meaningful score to report.
What does code coverage measure, and what does it miss?
It measures which lines ran; it misses whether any assertion would catch a bug in them.

Connections

  • Mutation testing — the parent topic these foundations unlock.
  • Unit testing — the tests we grade come from here.
  • Code coverage — the weaker metric introduced in Section 6.
  • Boundary value analysis — Section 2's number-line gap is exactly its domain.
  • Test-driven development — writes the tests mutation testing later audits.
  • Continuous integration — where the mutation run typically executes.
  • Fault injection — the broader "deliberately break it" family the mutant idea belongs to.