Maven – PITest突然変異テストの例

この記事では、Maven PIT mutation testing pluginを使用してJavaプロジェクトのミューテーションテストカバレッジレポートを生成する方法を示します。
でテスト済み
-
Maven 3.5.3
-
JUnit 5.3.1
-
PITest 1.4.3
Note
JaCoCoのようなラインカバレッジツールは、コードがテストされているかカバーされているかを通知するだけですが、このPITestミューテーションカバレッジはテストの有効性を通知しようとします。
1. クイック– Maven PITestプラグイン
1.1 To enable PIT mutation testing, put this pitest-maven in pom.xml
pom.xml
org.pitest pitest-maven 1.4.3 pit-report test mutationCoverage org.pitest pitest-junit5-plugin 0.8 com.example.examples.* com.example.examples.*
1.2 Run the PITest manually.
$ mvn clean org.pitest:pitest-maven:mutationCoverage
1.3 Above pom.xml file attached the ‘mutationCoverage’ goal to Maven test phase. これで、Mavenテストを実行すると、PITestテストが自動的にトリガーされます。
$ mvn clean test
1.4 Report will be generated at target/pit-reports/YYYYMMDDHHMI/*
2. 突然変異試験とは
2.1 The mutation testing is used to measure the effectiveness of the test.
ミューテーションテストでは、mutators(数学演算子の切り替え、戻り値の型の変更、呼び出しの削除など)を使用して、コードを別のミューテーションにミューテーション/変更し(ミューテーターに基づいて新しいコードを作成)、ユニットかどうかを確認します。新しいミューテーションのテストは失敗します(ミューテーションは強制終了されます)。
テストの有効性は、いくつの突然変異が殺されたかの尺度となり得る。
2.2 For example, this code :
public boolean isPositive(int number) {
boolean result = false;
if (number >= 0) {
result = true;
}
return result;
}
デフォルトでは、PITestは異なるmutatorsを使用して、上記のコードを異なるミューテーションに変換します(新しいコード):
#1突然変異–条件付き境界の変更(ミューテーター)
public boolean isPositive(int number) {
boolean result = false;
// mutator - changed conditional boundary
if (number > 0) {
result = true;
}
return result;
}
#2突然変異–否定条件付き(ミューテーター)
public boolean isPositive(int number) {
boolean result = false;
// mutator - negated conditional
if (false) {
result = true;
}
return result;
}
#3突然変異–整数サイズの値の戻り値を(x == 0? 1:0)(ミューテーター)
public boolean isPositive(int number) {
boolean result = false;
if (number > 0) {
result = true;
}
// mutator - (x == 0 ? 1 : 0)
return !result;
}
2.3 A Good unit test should fail (kill) all the mutations #1,#2,#3.
@Test
public void testPositive() {
CalculatorService obj = new CalculatorService();
assertEquals(true, obj.isPositive(10));
}
上記の単体テストは突然変異#2と#3を殺します(単体テストは失敗します)が、突然変異#1は生き残ります(単体テストに合格します)。

2.4 Review the mutation #1 again. このテスト(突然変異)を失敗(強制終了)するには、条件付き境界である数値ゼロをテストする必要があります。
public boolean isPositive(int number) {
boolean result = false;
// mutator - changed conditional boundary
if (number > 0) {
result = true;
}
return result;
}
数字のゼロをテストすることにより、単体テストを改善します。
@Test
public void testPositive() {
CalculatorService obj = new CalculatorService();
assertEquals(true, obj.isPositive(10));
//kill mutation #1
assertEquals(true, obj.isPositive(0));
}
完了、100%の突然変異カバレッジ。

3. Maven + PITestの例
自己参照のみを目的とした別の完全なMaven + PITestの例。
3.1 A full pom.xml
pom.xml
4.0.0 com.example.examples maven-mutation-testing jar 1.0-SNAPSHOT UTF-8 1.8 1.8 5.3.1 1.4.3 org.junit.jupiter junit-jupiter-engine ${junit.version} test maven-mutation-testing org.apache.maven.plugins maven-surefire-plugin 3.0.0-M1 org.pitest pitest-maven ${pitest.version} pit-report test mutationCoverage org.pitest pitest-junit5-plugin 0.8 com.example.examples.* com.example.examples.*
3.2 Source Code
StockService.java
package com.example.examples;
public class StockService {
private int qtyOnHand;
public StockService(int qtyOnHand) {
this.qtyOnHand = qtyOnHand;
}
private void isValidQty(int qty) {
if (qty < 0) {
throw new IllegalArgumentException("Quality should be positive!");
}
}
public int add(int qty) {
isValidQty(qty);
qtyOnHand = qtyOnHand + qty;
return qtyOnHand;
}
public int deduct(int qty) {
isValidQty(qty);
int newQty = qtyOnHand - qty;
if (newQty < 0) {
throw new IllegalArgumentException("Out of Stock!");
} else {
qtyOnHand = newQty;
}
return qtyOnHand;
}
}
3.3 Below unit test will kill all the mutations that are generated by PItest.
TestStockService.java
package com.example.examples;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestStockService {
@DisplayName("Test deduct stock")
@Test
public void testDeduct() {
StockService obj = new StockService(100);
assertEquals(90, obj.deduct(10));
assertEquals(0, obj.deduct(90));
assertEquals(0, obj.deduct(0));
Assertions.assertThrows(IllegalArgumentException.class, () -> {
obj.deduct(-1);
});
Assertions.assertThrows(IllegalArgumentException.class, () -> {
obj.deduct(100);
});
}
@DisplayName("Test add stock")
@Test
public void testAdd() {
StockService obj = new StockService(100);
assertEquals(110, obj.add(10));
assertEquals(110, obj.add(0));
Assertions.assertThrows(IllegalArgumentException.class, () -> {
obj.add(-1);
});
}
}
3.4 Run it.
$ mvn clean org.pitest:pitest-maven:mutationCoverage #or $ mvn clean test
3.5 Review the report at target\pit-reports\${YYYYMMDDHHMI}\index.html


4. FAQs
4.1 Study the PITest Mutators, so that we understand how the mutation is generated.
4.2 This mutation testing is a time consuming task, always declares the classes that are needed for the mutation test.
pom.xml
org.pitest pitest-maven ${pitest.version} com.example.examples.*Calculator* com.example.examples.*Stock* com.example.examples.*
ソースコードをダウンロード
$ git clone https://github.com/example/maven-examples.git
$ cd maven-mutation-testing
$ mvn clean org.pitest:pitest-maven:mutationCoverage
#or
$ mvn clean test