I wrote a simple program that given three integers representing the lengths of the sides of a triangle, outputs which type of triangle it is. Then I wrote a set of test cases and ran mutation coverage using pitest.
My goal is to obtain a 100% mutation coverage which I think is doable since it is a very simple program.
My problem is that pitest introduces mutations that I don't understand and thus don't know how to kill.
Here is the program:
public class TriangleType {
public String triangleType(int ab, int ac, int bc) {
if(ab <= 0 || ac <= 0 || bc <= 0) {
return "sign fail";
}
if(ab + ac <= bc || ab + bc <= ac || ac + bc <= ab) {
return "triangle inequality fail";
}
if(ab == ac && ab == bc) {
return "equilateral";
}
if(ab == ac || ab == bc || ac == bc) {
return "isoceles";
}
return "scalene";
}
}
Here are the test cases :
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class TriangleTypeTestMutation extends TriangleTypeTest{
protected TriangleType triangleType;
@BeforeEach
void setUp() {
triangleType = new TriangleType();
}
@Test
void testEquilateral() {
assertEquals("equilateral", triangleType.triangleType(1,1,1));
assertEquals("equilateral", triangleType.triangleType(7, 7, 7));
}
@Test
void testIsoceles() {
assertEquals("isoceles", triangleType.triangleType(5, 5, 2));
assertEquals("isoceles", triangleType.triangleType(2, 5, 5));
assertEquals("isoceles", triangleType.triangleType(5, 2, 5));
}
@Test
void testScalene() {
assertEquals("scalene", triangleType.triangleType(3, 5, 7));
assertEquals("scalene", triangleType.triangleType(10, 4, 7));
}
@Test
void testTriangleInequalityFail() {
//Should not answer scalene
assertEquals("triangle inequality fail", triangleType.triangleType(100, 3, 4));
assertEquals("triangle inequality fail", triangleType.triangleType(2, 50, 3));
assertEquals("triangle inequality fail", triangleType.triangleType(1, 2, 33));
//Should not answer isoceles
assertEquals("triangle inequality fail", triangleType.triangleType(100, 3, 3));
assertEquals("triangle inequality fail", triangleType.triangleType(2, 50, 2));
assertEquals("triangle inequality fail", triangleType.triangleType(1, 1, 33));
}
@Test
public void testKillConditionalBoundaryMutation() {
assertEquals("sign fail", triangleType.triangleType(0, 1, 2));
assertEquals("sign fail", triangleType.triangleType(1, 0, 2));
assertEquals("sign fail", triangleType.triangleType(1, 1, 0));
assertEquals("triangle inequality fail", triangleType.triangleType(1, 1, 2));
assertEquals("triangle inequality fail", triangleType.triangleType(2, 1, 1));
assertEquals("triangle inequality fail", triangleType.triangleType(1, 2, 1));
}
@Test
public void testKillLessOrEqualToEqual() {
assertEquals("sign fail", triangleType.triangleType(-1, 1, 5));
assertEquals("sign fail", triangleType.triangleType(1, -1, 5));
assertEquals("sign fail", triangleType.triangleType(1, 1, -5));
}
}
Here are some of the mutations that I don't understand :
For example about the line if(ab == ac && ab == bc){ return "equilateral";}
How can it mutate not equal to greater than if I have no not equal in my code?
Also, I have no local variable. Does it consider the arguments of the method as local variables?
Pitest mutates bytecode rather than source code. The mutators attempt to describe the equivalent change in the source file, but sometimes this is less than straightforward.
These mutations look to have been generated by the 'rv' mutators. These are generally of lower quality than the standard set and are not reccomended for general use. Do not use them unless you have a particular reason to do so.
The "incremented a local variable" should really read "incremented a the local variable or paramter". The operator would need to do additional analysis to work out which.
The not equal to greater than mutation will have mutated a IFNE instruction in the bytecode. In the simplest case this will map to a != check as the mutation description suggests, but the compiler may choose to use this instruction when generated other logic constructs (in this case equality checks combined with an &&). The description is certainly misleading, updating the mutator to make it more accurate would however involve writing the large part of a decompiler.