Mutation testing is the true test for whether or not your source code does the right thing, and whether it's properly tested by your unit tests. But while we're on a killing spree against the mutants of our source code, let's reflect a bit on what these mutants really represent, and why we want to include the unit tests that kill them in our test suite.
What's the real point of boundary testing? Do we really need every statement in the source code? Have we tested all calculations? How do we know that we got all the access modifiers on our methods right? These are some of the questions that we would like to reflect on for a moment.
We'll start this talk with a short introduction to mutation testing, but only to set the stage. The core of this talk consists of a walk-through of Java code and unit tests, and a reflection on TDD and the quality of our unit tests. At the end of this talk you should be more suspicious about both your source code and your unit tests.
16. Mutation Testing
• Unit tests testing the
source code
• Make a change to the
source code
• Failing unit test
16
17. Mutation Testing
• Unit tests testing the
source code
• Make a change to the
source code
• Failing unit test
• Many failing unit tests
17
18. Mutation Testing
• Unit tests testing the
source code
• Make a change to the
source code
• Failing unit test
• Many failing unit tests
• Infinite loops, compilation
errors, run-time errors,
etc…
18
22. Getters and Setters
• How difficult can it be?
• If I had a dime for every
time…
22 https://www.flickr.com/photos/mussikatz/16015546324/
23. Detach from the Input
• Isolate inner state from
input parameters
• Detach from input
parameters
• Arrays
• Collections
• Objects in general
23 https://www.flickr.com/photos/macieksz/503731332/
24. Detach the Output
• Don’t expose internal
state
• (Deep) copies
• Unmodifiable collections
24 https://www.flickr.com/photos/kelloggphotography/2099315965/
25. Unit Testing Getters, Setters, Constructors and Builders
• Are getters, setters, constructors and builders wired together
correctly?
• Primitive types, but also objects
• Are setters, constructors and builders detaching correctly from
input parameters?
• Arrays, collections, objects
• Are getters returning (deep) copies or unmodifiable versions?
25
41. Adding a Corner Case
assertTrue(square.isInside(0, 0));
assertFalse(square.isInside(2, 2));
assertTrue(square.isInside(1, 1));
41
42. Adding a Corner Case
assertTrue(square.isInside(0, 0));
assertFalse(square.isInside(2, 2));
assertTrue(square.isInside(1, 1));
→ 11 (+2) out of 17 mutants killed
42
59. Six Unit Tests
assertTrue(square.isInside(1, 1));
assertTrue(square.isInside(-1, -1));
assertFalse(square.isInside(-2, 0));
assertFalse(square.isInside(0, 2));
assertFalse(square.isInside(2, 0));
assertFalse(square.isInside(0, -2));
59
60. Unit Testing a Square
• How many unit tests do
you need?
• 0?
• 1?
• 2?
• 4?
• 8?
• 6
60 https://www.flickr.com/photos/jasoncooper/13204428724/
61. Unit Testing a Square
• How many unit tests do
you need?
• 0?
• 1?
• 2?
• 4?
• 8?
• At least 6
61 https://www.flickr.com/photos/jasoncooper/13204428724/
62. [-1, 1] × [-1, 1]
KISS implementation:
boolean isInside(double x, double y) {
return -1 <= x && x <= 1
&& -1 <= y && y <= 1;
}
62
63. [-1, 1] × [-1, 1]
A mutant (currently) not generated by
PIT:
boolean isInside(double x, double y) {
return -1 <= x && Math.floor(x) <= 1
&& -1 <= y && y <= 1;
}
63
64. [-1, 1] × [-1, 1]
A mutant (currently) not generated by
PIT:
boolean isInside(double x, double y) {
return -1 <= x && Math.floor(x) <= 1
&& -1 <= y && y <= 1;
}
64
65. [-1, 1] × [-1, 1]
A mutant (currently) not generated by
PIT:
boolean isInside(double x, double y) {
return -1 <= x && Math.floor(x) <= 1
&& -1 <= y && y <= 1;
}
65
70. Unit Testing a Square
• How many unit tests do
you need?
• 0?
• 1?
• 2?
• 4?
• 8?
• At least 6
70 https://www.flickr.com/photos/jasoncooper/13204428724/
73. Testing Conditionals
• Involves three things:
• Exercising the conditional
• Running the conditional
clause
• Verifying the functionality
of the conditional clause
73 https://www.flickr.com/photos/borkurdotnet/6797142125/
74. Testing Conditionals
• Involves three things:
• Exercising the conditional
• Running the conditional
clause
• Verifying the functionality
of the conditional clause
If you can’t reach the clause,
consider removing the code!
74 https://www.flickr.com/photos/borkurdotnet/6797142125/
83. How to Test Logging
• Create an in-memory
appender
• Set up the condition for
the message to be logged
• Check for:
• Its presence
• Log level
• Message content
83 https://www.flickr.com/photos/waltstoneburner/7946581522/
84. How to Test Exceptions
• Set up the condition for
the exception to be
thrown
• Check for:
• Its presence
• Exception type
• Message content
84 https://www.flickr.com/photos/7304439@N06/1453921573/
91. Testing Public APIs
• Functionality ≠ public API
• Testing in the same
package:
• Common practice
• JUnit, TestNG, …
• Maven conventions
• IDEs
• “Interface exhibitionism”
91 https://www.flickr.com/photos/neesam/28674222645/
92. Testing Public APIs
• Test functionality in the
same package
• Use the default access
modifier when needed
• Test the public API from a
different package
• To figure out which
interfaces, classes and
methods should be public
92 https://www.flickr.com/photos/neesam/28674222645/
93. Access Modifier Mutator
• I could wish for an access
modifier mutator:
• On every interface, class,
method, field, etc…
• public → protected
• protected → (package)
• (package) → private
93 https://www.flickr.com/photos/neesam/28674222645/