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