これは、なにをしたくて書いたもの?
Maven Surefire PluginのincludesFileを使って、テストクラスを分割して実行する方法を見てみました。
Maven Surefire Pluginでテストクラスを分割してファイルで指定してみる(includesFile) - CLOVER🍀
この時にJaCoCoでカバレッジを取ったとしたら、どういうことになるのか確認してみました。
お題
使用するソースコードおよびテストコードは、この時と同じにします。
Maven Surefire Pluginでテストクラスを分割してファイルで指定してみる(includesFile) - CLOVER🍀
つまり、こういうテスト対象コードと
src/main/java/org/littlewings/surefire/CalcService1.java
package org.littlewings.surefire; public class CalcService1 { public int plus(int a, int b) { return a + b; } public int minus(int a, int b) { return a - b; } }
こういうテストコードのペアを
src/test/java/org/littlewings/surefire/CalcService1Test.java
package org.littlewings.surefire; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class CalcService1Test { @Test void plus() { CalcService1 sut = new CalcService1(); assertEquals(5, sut.plus(3, 2)); } @Test void minus() { CalcService1 sut = new CalcService1(); assertEquals(2, sut.minus(8, 6)); } }
10組用意します。
$ tree src
src
├── main
│ ├── java
│ │ └── org
│ │ └── littlewings
│ │ └── surefire
│ │ ├── CalcService1.java
│ │ ├── CalcService10.java
│ │ ├── CalcService2.java
│ │ ├── CalcService3.java
│ │ ├── CalcService4.java
│ │ ├── CalcService5.java
│ │ ├── CalcService6.java
│ │ ├── CalcService7.java
│ │ ├── CalcService8.java
│ │ └── CalcService9.java
│ └── resources
└── test
└── java
└── org
└── littlewings
└── surefire
├── CalcService10Test.java
├── CalcService1Test.java
├── CalcService2Test.java
├── CalcService3Test.java
├── CalcService4Test.java
├── CalcService5Test.java
├── CalcService6Test.java
├── CalcService7Test.java
├── CalcService8Test.java
└── CalcService9Test.java
11 directories, 20 files
そして、テストコードを3グループに分割してそれぞれ実行した時にカバレッジレポートがどうなるかを考えていこうと思います。
# 分割 $ find src/test/java -name '*.java' | perl -wp -e 's!src/test/java/!!' > test_classes.txt $ split -n 'l/3' -d test_classes.txt test_classes_group_ # テスト実行 $ mvn test -Dsurefire.includesFile=test_classes_group_00 $ mvn test -Dsurefire.includesFile=test_classes_group_01 $ mvn test -Dsurefire.includesFile=test_classes_group_02
環境
今回の環境はこちら。
$ java --version openjdk 21.0.4 2024-07-16 OpenJDK Runtime Environment (build 21.0.4+7-Ubuntu-1ubuntu222.04) OpenJDK 64-Bit Server VM (build 21.0.4+7-Ubuntu-1ubuntu222.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.4, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.15.0-124-generic", arch: "amd64", family: "unix"
シンプルにカバレッジを取得してみる
まずはシンプルにJaCoCoでカバレッジを取得してみましょう。
JaCoCoのWebサイトはこちら。
EclEmma - JaCoCo Java Code Coverage Library
JaCoCo Maven Pluginのドキュメントはこちら。
サンプルはこちら。
https://www.jacoco.org/jacoco/trunk/doc/examples/build/pom.xml
サンプルに習って、ひとまずJaCoCoの設定をしておきます。
<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.11.3</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.1</version> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.12</version> <executions> <execution> <id>prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
テストを実行。
$ mvn test
JaCoCoが組み込まれてテストが実行されます。
[INFO] --- jacoco:0.8.12:prepare-agent (prepare-agent) @ jacoco-surefire-tests-split-merge --- [INFO] argLine set to -javaagent:$HOME/.m2/repository/org/jacoco/org.jacoco.agent/0.8.12/org.jacoco.agent-0.8.12-runtime.jar=destfile=/path/to/target/jacoco.exec [INFO]
JaCoCoのカバレッジデータはこちらです。
$ ll target/jacoco.exec -rw-rw-r-- 1 xxxxx xxxxx 25591 10月 26 19:05 target/jacoco.exec
カバレッジレポート作成。
$ mvn jacoco:report
target/site/jacocoディレクトリにindex.htmlができるので、こちらを確認。

OKですね。
ここまでが最初の準備です。
1度取得したカバレッジのデータやレポートは削除しておきます。
$ mvn clean
テストを分割して実行して、カバレッジデータがどうなるか確認してみる
次に、テストを分割して実行してみましょう。これでテストの実行を進めていくと、カバレッジレポートがどうなっていくのか確認して
みたいと思います。
まずはテストクラスを3つのグループに分割。
$ find src/test/java -name '*.java' | perl -wp -e 's!src/test/java/!!' > test_classes.txt $ split -n 'l/3' -d test_classes.txt test_classes_group_
以下の3つのファイルに分かれました。
test_classes_group_00
org/littlewings/surefire/CalcService1Test.java org/littlewings/surefire/CalcService5Test.java org/littlewings/surefire/CalcService7Test.java org/littlewings/surefire/CalcService2Test.java
test_classes_group_01
org/littlewings/surefire/CalcService3Test.java org/littlewings/surefire/CalcService6Test.java org/littlewings/surefire/CalcService10Test.java
test_classes_group_02
org/littlewings/surefire/CalcService4Test.java org/littlewings/surefire/CalcService9Test.java org/littlewings/surefire/CalcService8Test.java
これを順番に実行していきます。
まずはひとつめ。
$ mvn test -Dsurefire.includesFile=test_classes_group_00
実行されたテスト。
[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running org.littlewings.surefire.CalcService5Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.079 s -- in org.littlewings.surefire.CalcService5Test [INFO] Running org.littlewings.surefire.CalcService2Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 s -- in org.littlewings.surefire.CalcService2Test [INFO] Running org.littlewings.surefire.CalcService7Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.010 s -- in org.littlewings.surefire.CalcService7Test [INFO] Running org.littlewings.surefire.CalcService1Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.011 s -- in org.littlewings.surefire.CalcService1Test [INFO] [INFO] Results: [INFO] [INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 [INFO]
カバレッジデータ。
$ ll target/jacoco.exec -rw-rw-r-- 1 xxxxx xxxxx 24965 10月 26 19:06 target/jacoco.exec
レポートを生成してみます。
$ mvn jacoco:report
当然ですが、実行したテストに関するもののみのカバレッジになっています。

では、この状態で次のグループを実行。
$ mvn test -Dsurefire.includesFile=test_classes_group_01
実行されたテスト。
[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running org.littlewings.surefire.CalcService3Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.076 s -- in org.littlewings.surefire.CalcService3Test [INFO] Running org.littlewings.surefire.CalcService6Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 s -- in org.littlewings.surefire.CalcService6Test [INFO] Running org.littlewings.surefire.CalcService10Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.008 s -- in org.littlewings.surefire.CalcService10Test [INFO] [INFO] Results: [INFO] [INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 [INFO]
カバレッジデータ。なんかだいぶ大きくなりましたね?
$ ll target/jacoco.exec -rw-rw-r-- 1 xxxxx xxxxx 49828 10月 26 19:07 target/jacoco.exec
レポートを作成。
$ mvn jacoco:report
結果どうなったかというと、追記になったようです。

最後のグループを実行。
$ mvn test -Dsurefire.includesFile=test_classes_group_02
実行されたテスト。
[INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running org.littlewings.surefire.CalcService9Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.111 s -- in org.littlewings.surefire.CalcService9Test [INFO] Running org.littlewings.surefire.CalcService4Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 s -- in org.littlewings.surefire.CalcService4Test [INFO] Running org.littlewings.surefire.CalcService8Test [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.010 s -- in org.littlewings.surefire.CalcService8Test [INFO] [INFO] Results: [INFO] [INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 [INFO]
JaCoCoのカバレッジデータ。1回で全部実行した時の3倍くらいの大きさになりましたね。
$ ll target/jacoco.exec -rw-rw-r-- 1 xxxxx xxxxx 74689 10月 26 19:07 target/jacoco.exec
レポートを作成すると
$ mvn jacoco:report
全部揃います。

というわけで、カバレッジデータは追記されるようです。
この動作はappendというパラメーターで制御するようなのですが、デフォルト値が書かれていません…。
If set to true and the execution data file already exists, coverage data is appended to the existing file. If set to false, an existing execution data file will be replaced.
実際の動作で確認できていますが、デフォルト値はtrueですね。
appendをfalseに設定してみる
では、appendをfalseにするとどうなるか確認してみます。
設定を以下のように変更。
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.12</version> <executions> <execution> <id>prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <goals> <goal>report</goal> </goals> </execution> </executions> <configuration> <append>false</append> </configuration> </plugin>
1度結果を削除して
$ mvn clean
最初のグループを実行してカバレッジレポートを確認。
$ mvn test -Dsurefire.includesFile=test_classes_group_00 $ ll target/jacoco.exec -rw-rw-r-- 1 xxxxx xxxxx 24965 10月 26 19:13 target/jacoco.exec $ mvn jacoco:report

次のグループを実行。
$ mvn test -Dsurefire.includesFile=test_classes_group_01 $ ll target/jacoco.exec -rw-rw-r-- 1 xxxxx xxxxx 24863 10月 26 19:14 target/jacoco.exec $ mvn jacoco:report
jacoco.execのサイズが減ったことからもわかるように、カバレッジデータが上書きされています。

これでJaCoCoのカバレッジデータがデフォルトでは追記で、無効にすると上書きになることが確認できましたね。
カバレッジデータを分割して取得し、マージしてみる
もともとこちらのエントリーを書いたのは、テストの実行を分割して並列化などできないだろうかという背景がありました。
Maven Surefire Pluginでテストクラスを分割してファイルで指定してみる(includesFile) - CLOVER🍀
なので、テストのグループごとに実行するサーバーが異なるなどもあるわけです。
この場合、カバレッジデータはそれぞれに取得してマージすることになりますね。このような用途では、JaCoCoのmergeゴールを使うと
よさそうです。
mergeゴールは、複数のデータファイル(*.exec)をひとつのファイルにマージします。
今回はこんな設定にしました。
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.12</version> <executions> <execution> <id>prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> <configuration> <destFile>${project.build.directory}/jacoco-split${test.group.index}.exec</destFile> </configuration> </execution> <execution> <id>report</id> <goals> <goal>report</goal> </goals> </execution> </executions> <configuration> <fileSets> <fileSet> <directory>${project.build.directory}</directory> <includes> <include>jacoco-split*.exec</include> </includes> </fileSet> </fileSets> </configuration> </plugin>
prepare-agentゴールでは、テスト実行時のカバレッジデータは${project.build.directory}/jacoco-split${test.group.index}.exec
というファイルに出力するように変更しています。
<execution> <id>prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> <configuration> <destFile>${project.build.directory}/jacoco-split${test.group.index}.exec</destFile> </configuration> </execution>
${test.group.index}というのは、自分で追加したプロパティです。実行するテストのグループと同じインデックスを指定するイメージです。
<properties> ... <test.group.index></test.group.index> </properties>
mergeゴール向けに、マージ対象を設定。
<configuration> <fileSets> <fileSet> <directory>${project.build.directory}</directory> <includes> <include>jacoco-split*.exec</include> </includes> </fileSet> </fileSets> </configuration>
destFileをprepare-agentのみに指定しているので、mergeゴールではカバレッジデータはデフォルトのjacoco.execとして出力されます。
ちなみに、fileSetsをgoalをmergeに設定したexecutionとconfigurationの中に追加しても認識してくれませんでした…。
というわけで、各グループのテストを実行。
$ mvn test -Dsurefire.includesFile=test_classes_group_00 -Dtest.group.index=0 $ mvn test -Dsurefire.includesFile=test_classes_group_01 -Dtest.group.index=1 $ mvn test -Dsurefire.includesFile=test_classes_group_02 -Dtest.group.index=2
それぞれのカバレッジデータの出力先が、destFileとプロパティで指定した値になっていることがわかります。
[INFO] --- jacoco:0.8.12:prepare-agent (prepare-agent) @ jacoco-surefire-tests-split-merge --- [INFO] argLine set to -javaagent:$HOME/.m2/repository/org/jacoco/org.jacoco.agent/0.8.12/org.jacoco.agent-0.8.12-runtime.jar=destfile=/path/to/target/jacoco-split0.exec [INFO] --- jacoco:0.8.12:prepare-agent (prepare-agent) @ jacoco-surefire-tests-split-merge --- [INFO] argLine set to -javaagent:$HOME/.m2/repository/org/jacoco/org.jacoco.agent/0.8.12/org.jacoco.agent-0.8.12-runtime.jar=destfile=/path/to/target/jacoco-split1.exec [INFO] [INFO] --- jacoco:0.8.12:prepare-agent (prepare-agent) @ jacoco-surefire-tests-split-merge --- [INFO] argLine set to -javaagent:$HOME/.m2/repository/org/jacoco/org.jacoco.agent/0.8.12/org.jacoco.agent-0.8.12-runtime.jar=destfile=/path/to/target/jacoco-split2.exec [INFO]
出力されたカバレッジデータ。
$ ll target/jacoco-split* -rw-rw-r-- 1 xxxxx xxxxx 24965 10月 26 19:42 target/jacoco-split0.exec -rw-rw-r-- 1 xxxxx xxxxx 24863 10月 26 19:43 target/jacoco-split1.exec -rw-rw-r-- 1 xxxxx xxxxx 24861 10月 26 19:43 target/jacoco-split2.exec
ちなみに、この時点ではレポートを作成しようとしても
$ mvn jacoco:report
カバレッジデータを認識しません。
[INFO] --- jacoco:0.8.12:report (default-cli) @ jacoco-surefire-tests-split-merge --- [INFO] Skipping JaCoCo execution due to missing execution data file.
reportゴールが読み取るカバレッジデータファイルはdataFileで指定しますが、このデフォルトもjacoco.execだからですね。
では、カバレッジデータをマージしてみます。
$ mvn jacoco:merge
このようにマージされたようです。
[INFO] --- jacoco:0.8.12:merge (default-cli) @ jacoco-surefire-tests-split-merge --- [INFO] Loading execution data file /path/to/target/jacoco-split1.exec [INFO] Loading execution data file /path/to/target/jacoco-split0.exec [INFO] Loading execution data file /path/to/target/jacoco-split2.exec [INFO] Writing merged execution data to /path/to/target/jacoco.exec
カバレッジレポートを出力してみます。
$ mvn jacoco:report
マージされたカバレッジデータを認識して
[INFO] --- jacoco:0.8.12:report (default-cli) @ jacoco-surefire-tests-split-merge --- [INFO] Loading execution data file /path/to/target/jacoco.exec [INFO] Analyzed bundle 'jacoco-surefire-tests-split-merge' with 10 classes
無事、マージされたレポートになりました。

ちなみに、こんな感じで個々のカバレッジデータを指定すると、その単位のカバレッジレポートを作成することはできます。
$ mvn jacoco:report -Djacoco.dataFile=target/jacoco-split0.exec
おわりに
JaCoCoでカバレッジデータの追記とマージを行ってみました。
カバレッジデータの追記はデフォルトの動作でしたが、実際に利用する時はその動作を押さえておいた方がよさそうですね。
カバレッジデータのマージについては、ひとつのモジュールのカバレッジを分割して取得することになった時向けに覚えておけばよいかなと。