Can You Trust Your Tests? 2016 Vaidas Pilkauskas & Tadas Ščerbinskas An Introduction to Mutation Testing
CanYou Trust Your Tests?
2016 Vaidas Pilkauskas & Tadas Ščerbinskas
An Introduction to Mutation Testing
“Program testing can be used to show the presence of bugs, but never to
show their absence!”- Edsger W. Dijkstra
Branches
String foo(boolean arg) { return arg ? "a" : "b";}
assertThat(a.foo(true), is("a"));
assertThat(a.foo(false), is("b"));
Can you trust 100% coverage?
Code coverage can only show what is not tested.
For interpreted languages 100% code coverage is equivalent to compilation.
What exactly is a mutation?
Boolean isFoo(int a) { return a == foo;}
Boolean isFoo(int a) { return a != foo;}
Boolean isFoo(int a) { return true;}
Boolean isFoo(int a) { return null;}
>>>
Terminology
Applying a mutation to some code creates a mutant.
If test passes - mutant has survived.
If test fails - mutant is killed.
// testassertThat(Max.of(0), equalTo(0)); ✔
// implementationpublic static int of(int... integers) { return 0;}
// testassertThat(Max.of(0), equalTo(0)); ✔assertThat(Max.of(1), equalTo(1)); ✘
// implementationpublic static int of(int... integers) { return 0;}
// testassertThat(Max.of(0), equalTo(0)); ✔assertThat(Max.of(1), equalTo(1)); ✔
// implementationpublic static int of(int... integers) { return integers[0];}
// testassertThat(Max.of(0), equalTo(0)); ✔assertThat(Max.of(1), equalTo(1)); ✔assertThat(Max.of(1, 2), equalTo(2)); ✘
// implementationpublic static int of(int... integers) { return integers[0];}
// testassertThat(Max.of(0), equalTo(0)); ✔assertThat(Max.of(1), equalTo(1)); ✔assertThat(Max.of(1, 2), equalTo(2)); ✔
// implementationpublic static int of(int... integers) { int max = integers[0]; for (int i : integers) if (max < i) max = i; return max;}
Mutation// testassertThat(Max.of(0), equalTo(0)); ✔assertThat(Max.of(1), equalTo(1)); ✔assertThat(Max.of(1, 2), equalTo(2)); ✔
// implementationpublic static int of(int... integers) { int max = integers[0]; for (int i : integers) if (max < i) max = i; return max;}
Mutation// testassertThat(Max.of(0), equalTo(0)); ✔assertThat(Max.of(1), equalTo(1)); ✔assertThat(Max.of(1, 2), equalTo(2)); ✔
// implementationpublic static int of(int... integers) { int max = integers[0]; for (int i : integers) if (true) max = i; return max;}
Mutation// testassertThat(Max.of(0), equalTo(0)); ✔assertThat(Max.of(1), equalTo(1)); ✔assertThat(Max.of(1, 2), equalTo(2)); ✔
// implementationpublic static int of(int... integers) { return integers[integers.length - 1];}
Mutation// testassertThat(Max.of(0), equalTo(0)); ✔assertThat(Max.of(1), equalTo(1)); ✔assertThat(Max.of(1, 2), equalTo(2)); ✔assertThat(Max.of(2, 1), equalTo(2)); ✘
// implementationpublic static int of(int... integers) { return integers[integers.length - 1];}
Mutation// testassertThat(Max.of(0), equalTo(0)); ✔assertThat(Max.of(1), equalTo(1)); ✔assertThat(Max.of(1, 2), equalTo(2)); ✔assertThat(Max.of(2, 1), equalTo(2)); ✔
// implementationpublic static int of(int... integers) { int max = integers[0]; for (int i : integers) if (max < i) max = i; return max;}
What if mutant survives
● Simplify your code● Add additional tests
● TDD - minimal amount of code to pass the test
Equivalent mutations
// Originalint i = 0;while (i != 10) { doSomething(); i += 1;}
// Mutantint i = 0;while (i < 10) { doSomething(); i += 1;}
● Let’s say we have codebase with:● 300 classes● around 10 tests per class● 1 test runs around 1ms● total test suite runtime is about 3s
Is it slow?
Let’s do 10 mutations per class● We get 3000 (300 classes * 10
mutations) mutations● runtime with all mutations is 150 minutes (3s * 3000)
Speeding it up
Run only tests that cover the mutation● 300 classes● 10 tests per class● 10 mutations per class● 1ms test runtime● total mutation runtime
10 tests * 10 mutations * 1 ms * 300 classes = 30 s
● Continuous integration● TDD with mutation testing only
on new changes● Add mutation testing to your
legacy project, but do not fail a build - produce a warning report
Usage scenarios
Maven usage
mvn org.pitest:pitest-maven:mutationCoverage
With SCM plugin
mvn org.pitest:pitest-maven:scmMutationCoverage \
-Dinclude=ADDED,UNKNOWN -DmutationThreshold=85
Configuration
● Dependency distance● Limit mutation number per class● Activate/deactivate mutators
● Extension points - Java SPI
What about TDD?
● Influences code style● Helps to spot dead features● Puts emphasis on minimal code
to pass the test
Summary
Code coverage highlights code that is not tested.
It shows which code you have executed in tests.
Summary
Mutation testing highlights code that is tested.
It shows which code you have asserted in tests.
Vaidas Pilkauskas
@liucijus
● Vilnius JUG co-founder
● Vilnius Scala leader
● Coderetreat facilitator
● Mountain bicycle rider
● Snowboarder
About me