Unit test的Line coverage很容易达到100%,但是并不能衡量测试的质量。Mutation test的引入就是解决这个问题。它通过修改程序然后跑现有的tests,如果tests没有fail的话,表示mutation coverage不好,所以mutation test是反着(每次否定一个语句)来跑测试的。Java的mutation test的框架主要是Pitest。
概念
Mutant: 每一个代码改动Mutation: 修改了的程序
Killed: 一个mutation不能通过unit test,这叫此mutation被杀死了,这是我们想见到的。
Survived: 一个mutation还能通过unit test,这叫此mutation存活了。
下面这个程序是判断一个字符串是否Palindrome。用首尾字符比较然后向中心字符串递归。
public boolean isPalindrome(String inputString) {
if (inputString.length() == 0) {
return true;
} else {
char firstChar = inputString.charAt(0);
char lastChar = inputString.charAt(inputString.length() - 1);
String mid = inputString.substring(1, inputString.length() - 1);
return (firstChar == lastChar)
&& isPalindrome(mid);
}
}
Unit test如下,它的code coverage是100%,但是它mutation coverage只有6/8。它两个地方没有被NEGATE_CONDITIONALS_MUTATOR杀死。
@Test
public void whenPalindrom_thenAccept() {
Palindrome palindromeTester = new Palindrome();
assertTrue(palindromeTester.isPalindrome("noon"));
}
第一处,若程序修改成 if (inputString.length() != 0),单元测试仍可以通过,因为任何非空字符串包括noon直接返回true了。这表示我们缺少failure test,加入下面这个:
@Test
public void whenNotPalindrom_thanReject() {
Palindrome palindromeTester = new Palindrome();
assertFalse(palindromeTester.isPalindrome("box"));
}
加入了第二个测试后。第二处,也就是程序改成&& !isPalindrome(mid),仍然survived,比较tricky。oo -->false, noon->true & !f(oo)= true, 双重否定后仍然是成立。所以加入只有有一组首尾相等的测试如neon即可。
@Test
public void whenNearPalindrom_thanReject() {
Palindrome palindromeTester = new Palindrome();
assertFalse(palindromeTester.isPalindrome("neon"));
}
Mutator
包括7个默认的MutatorConditionals Boundary Mutator。 如<= to <
Increments Mutator。如i++ to i--
Invert Negatives Mutator。如return i to return -i
Math Mutator。如b+c to b-c
Negate Conditionals Mutator。如== to !=,这个是最普遍的会survive的。
Return Values Mutator。如return new Object() to return null
Void Method Calls Mutator。如someMethod() to (method removed)。这个也普遍。
其他Mutator
Constructor Call Mutator。如Object o = new Object to Object o = null
Empty returns Mutator。如return "abc" to return ""
Config
--threads表示用多少个线程跑--mutators用什么mutator
--coverageThreshold测试覆盖率
--targetClasses只测试某个包下面的class
--targetTests只测试某个TestClass
--timeoutConst某个测试的时间上限。这个影响全部测试的时间,因为有些mutation可能会导致死循环,当然也算是failed的。
Incremental Analysis
可以配置来减少每次跑测试的时间。因为通过比较代码改动和上次测试结果,新一轮测试只会incremental跑。Ref
http://pitest.org/quickstart/mutators/https://www.baeldung.com/java-mutation-testing-with-pitest
https://www.testwo.com/article/869
java编程初学者的示例代码
ReplyDelete使用文本消息bean示例代码