public class SampleSmallClassTest { @Test public void testStuff() { // The boss says we should write tests. // Doesn't seem that hard... assertTrue(true); } }
$ gradle test --tests *testStuff jacocoTestReport
Element | Missed Instructions | Coverage |
doSomeMath(int, int) |
|
0% |
@Test public void testStuffWithCoverage() { // Need to get that coverage thing high... for(int a = -5; a <= 5; a ++) { for (int b = -5; b <= 5; b++) { SampleSmallClass.doSomeMath(a, b); } } }
$ gradle test --tests *testStuffWithCoverage jacocoTestReport
Element | Missed Instructions | Coverage |
doSomeMath(int, int) |
|
100% |
Tool | Java | Ant | Maven | Gradle | CI | TestNG | Mocks* | Last Update |
---|---|---|---|---|---|---|---|---|
Simple Jester | 5-6 | N | N | N | N | N | ? | 2009 |
javaLanche | 5-6 | N | N | N | N | N | ? | 2011 |
Jumble | 4-6 | Y | N | N | N | Y | 3/6 | 2013 |
Pitest | 5-8 | Y | Y | Y | Y | Y | 6/6 | Yesterday |
$ gradle test --tests *testStuffWithCoverage pitest
Name | Line Coverage | Mutation Coverage |
Total | 100% | 0% |
SampleSmallClass.java |
|
|
public static int doSomeMath(int a, int b) { long result = b; result += a; result -= b; result -= a; return (int) result; }
public static int doSomeMath(int a, int b) {
long result = b;
result -= a;
result -= b;
result -= a;
return (int) result;
}
Replaced long addition with subtraction → SURVIVED
public static int doSomeMath(int a, int b) {
long result = b;
result += a;
result -= b;
result += a;
return (int) result;
}
Replaced long subtraction with addition → SURVIVED
public static int doSomeMath(int a, int b) {
long result = b;
result += a;
result -= b;
result -= a;
return ((int) result == 0 ? 1 : 0);
}
Replaced return value with (x == 0 ? 1 : 0) → SURVIVED
@Test
public void testProperly() {
final int expected = 0;
for(int a = -5; a <= 5; a ++) {
for(int b = -5; b <= 5; b ++) {
assertEquals(expected, doSomeMath(a, b));
}
}
}
$ gradle test --tests *testProperly pitest
Name | Line Coverage | Mutation Coverage |
Total | 100% | 100% |
SampleSmallClass.java |
|
|
What we want to have:
if(isAllowed(action, user)) { action.execute(); }
if(!isAllowed(action, user)) {
action.execute();
}
if(remainingHp <= 0) { kill(player); }
if(remainingHp < 0) {
kill(player);
}
if(cacheEnabled()) { cache.store(result); }
cache.store(result);
if(isValid(authData)) { login(authData); } else { throw new GtfoException(); }
throw new GtfoException();
int sum = a + b;
int sum = a - b;
NB: Also affects field increments and decrements
private int total; void add(double sample) { total ++; }
→
0: aload_0
1: dup
2: getfield
5: iconst_1
6: iadd # ← addition
7: putfield
int total = 0; for(...) { total ++; }
int total = 0;
for(...) {
total --;
}
if(profit < 0) { loss = -profit; }
if(profit < 0) {
loss = profit;
}
int total = 0; for(...) { total ++; }
int total = 0;
for(...) {
}
for(Listener l : listeners) { l.onStuffHappened(stuff); }
for(Listener l : listeners) {
}
double len = hypot(a, b);
double len = 0.0;
Product p = new Product(...); //... double result = calculate(p);
Product p = null;
//...
double result = calculate(p);
User findOwner(...) { //... return possibleOwner; }
//...if(possibleOwner == null) throw new RuntimeException(); else return null;
void consume(Object obj) { int tlr = (this.tlr = (this.tlr * 1664525 + 1013904223)); //... }
void consume(Object obj) {
int tlr = (this.tlr =
(this.tlr * 1664525 +
1013904224));
//...
}
void stop() { this.shouldStop = true; }
void stop() {
}
switch(condition) { case 1: doFirst(); break; case 2: doSecond(); break; default: doDefault(); break; }
switch(condition) { case 1: doDefault(); break; case 2: doDefault(); break; default: doFirst(); break; }
switch(condition) { case 1: doFirst(); break; case 2: doSecond(); break; default: doDefault(); break; }
doDefault();
A success story from TheLadders.com
$ svn checkout http://svn.apache.org/repos/asf/comm [...] $ cd commons-math3 $ patch commons-math3/pom.xml commons-math3-pom-patch.diff
$ mvn clean test [...] Tests run: 4901, Failures: 0, Errors: 0, Skipped: 43 [INFO] --------------------------------------------- [INFO] BUILD SUCCESS [INFO] --------------------------------------------- [INFO] Total time: 05:17 min
$ mvn clean test pitest:mutationCoverage [...] ====================================================== - Timings ====================================================== > scan classpath : < 1 second > coverage and dependency analysis : 27 minutes and 31 seconds > build mutation tests : 7 seconds > run mutation analysis : 3 hours, 47 minutes and 7 seconds ====================================================== > Total : 4 hours, 14 minutes and 46 seconds
$ mvn clean test pitest:mutationCoverage -Dpit.threads=2 [...] ====================================================== - Timings ====================================================== > scan classpath : < 1 second > coverage and dependency analysis : 27 minutes and 28 seconds > build mutation tests : 3 seconds > run mutation analysis : 2 hours, 2 minutes and 1 seconds ====================================================== > Total : 2 hours, 29 minutes and 33 seconds
Store the following info between runs:
Do not check a mutant, if in the previous run:
$ mvn ... pitest:mutationCoverage -P pit-history -Dpit.threads=4 [...] ====================================================== - Timings ====================================================== > scan classpath : < 1 second > coverage and dependency analysis : 7 minutes and 15 seconds > build mutation tests : 2 seconds > run mutation analysis : 55 minutes and 41 seconds ====================================================== > Total : 1 hours, 2 minutes and 59 seconds
o.a.c.math3.linear.MatrixUtils
public static <T extends FieldElement<T>> FieldMatrix<T> createFieldIdentityMatrix(final Field<T> field, final int dimension) { final T zero = field.getZero(); final T one = field.getOne(); final T[][] d = MathArrays.buildArray(field, dimension, dimension); for (int row = 0; row < dimension; row++) { final T[] dRow = d[row]; Arrays.fill(dRow, zero); dRow[row] = one; } return new Array2DRowFieldMatrix<T>(field, d, false); }
public static <T extends FieldElement<T>> FieldMatrix<T>
createFieldIdentityMatrix(final Field<T> field, final int dimension) {
final T zero = field.getZero();
final T one = field.getOne();
final T[][] d = MathArrays.buildArray(field, dimension, dimension);
for (int row = 0; row < dimension; row++) {
final T[] dRow = d[row];
dRow[row] = one;
}
return new Array2DRowFieldMatrix<T>(field, d, false);
}
200: removed call to java/util/Arrays::fill : SURVIVED
$ mvn ... pitest:mutationCoverage -P pit-history -Dpit.threads=4 [...] ====================================================== - Timings ====================================================== > scan classpath : < 1 second > coverage and dependency analysis : 7 minutes and 3 seconds > build mutation tests : 1 minutes and 1 seconds > run mutation analysis : 56 seconds ====================================================== > Total : 9 minutes and 0 seconds
public int evaluate(final int distance) { if(distance == 0) { throw new IllegalArgumentException("Distance should be non-zero"); } int result = 0; if(distance > 0) { result += calculateDistanceAdjustment(distance); } return result; }
public int evaluate(final int distance) { if(distance == 0) { throw new IllegalArgumentException("Distance should be non-zero"); } int result = 0; if(distance > 0) { result += calculateDistanceAdjustment(distance); } return result; }
11: changed conditional boundary → SURVIVED
public class Minitrue { public static void rectify(String path) { ensurePathIsSecure(path); rmRf(path); } private static final void rmRf(String path) { String command = "$ rm -rf " + path; System.out.println("Executing: " + command); exec(command); } // ... }
@Test public void testRectifySecurity() { boolean rejected = false; try { Minitrue.rectify("/"); } catch(SecurityException e) { rejected = true; } Assert.assertTrue(rejected); Assert.assertFalse(Minitrue.isUnpath("/")); }
stderr : PIT >> Running mutation [...]
mutator=VoidMethodCallMutator,
description=removed call to Minitrue::ensurePathIsSecure
stderr : PIT >> mutating method rectify
stderr : PIT >> 2 relevant test for rectify
stderr : PIT >> Running 1 units
stdout : Executing: rm -rf /