以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2024/10/26/195721より取得しました。


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に設定してみる

では、appendfalseにするとどうなるか確認してみます。

設定を以下のように変更。

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

destFileprepare-agentのみに指定しているので、mergeゴールではカバレッジデータはデフォルトのjacoco.execとして出力されます。

ちなみに、fileSetsgoalmergeに設定したexecutionconfigurationの中に追加しても認識してくれませんでした…。

というわけで、各グループのテストを実行。

$ 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でカバレッジデータの追記とマージを行ってみました。

カバレッジデータの追記はデフォルトの動作でしたが、実際に利用する時はその動作を押さえておいた方がよさそうですね。

カバレッジデータのマージについては、ひとつのモジュールのカバレッジを分割して取得することになった時向けに覚えておけばよいかなと。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2024/10/26/195721より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14