CLOVER🍀

That was when it all began.

JaCoCoでカバレッゞデヌタの远蚘、マヌゞを行う

これは、なにをしたくお曞いたもの

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のドキュメントはこちら。

JaCoCo - Maven Plug-in

サンプルはこちら。

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.

JaCoCo - jacoco:prepare-agent

実際の動䜜で確認できおいたすが、デフォルト倀はtrueですね。

https://github.com/jacoco/jacoco/blob/v0.8.12/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java#L281-L288

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ゎヌルを䜿うず
よさそうです。

JaCoCo - 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>

JaCoCo - jacoco:prepare-agent

${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だからですね。

JaCoCo - jacoco:report

では、カバレッゞデヌタをマヌゞしおみたす。

$ 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でカバレッゞデヌタの远蚘ずマヌゞを行っおみたした。

カバレッゞデヌタの远蚘はデフォルトの動䜜でしたが、実際に利甚する時はその動䜜を抌さえおおいた方がよさそうですね。

カバレッゞデヌタのマヌゞに぀いおは、ひず぀のモゞュヌルのカバレッゞを分割しお取埗するこずになった時向けに芚えおおけばよいかなず。