CLOVER🍀

That was when it all began.

Maven Surefire Pluginでテストクラスを分割してファイルで指定してみる(includesFile)

これは、なにをしたくて書いたもの?

前に、こんなエントリーを書きました。

Maven Surefire Pluginのincludeになにを指定したらいいのかわからないという話 - CLOVER🍀

この時はMaven Surefire Pluginのincluesincludeexcludesexcludeになにを指定すればよいのかを確認してみました。

この指定をファイルで行う方法があるようなので、今回はこちらを試してみます。

Maven Surefire PluginのincludesFileとexcludesFile

incluesincludeexcludesexclude自体については、こちらに書かれています。

Maven Surefire Plugin – Inclusions and Exclusions of Tests

また、なにを指定すればよいのかはこちらで確認してみました。

Maven Surefire Pluginのincludeになにを指定したらいいのかわからないという話 - CLOVER🍀

こちらのエントリーを書いていた時、これらの指定をファイルで行えることに気づいていたので今回はこちらを試してみようかなという話です。

書き方自体はincluesincludeexcludesexcludeと同じで、Maven Surefire Plugin 3.0.0-M6以降はテストメソッドの指定もできるようです。

空行や#で始まる行は無視され、すでにincludesexcludesを指定している場合はファイルで指定したパターンが追加されるようです。

A file containing include patterns. Blank lines, or lines starting with # are ignored. If includes are also specified, these patterns are appended. Example with path, simple and regex includes:

A file containing exclude patterns. Blank lines, or lines starting with # are ignored. If excludes are also specified, these patterns are appended. Example with path, simple and regex excludes:

今回はincludesFileを使ってみます。

なにをしたいか?

テストコードが増えていくと、テストの実行速度が問題になることがあります。

テストの実行自体を速くするのはもちろん必要ですが、テストを分割して並列実行というアプローチもあると思うので、そちらに
利用できないかなと思いまして。

というわけで、適当にいくつかテスト対象のコードとテストコードを書き、includesFileを使ってテストクラス群を分割して実行して
みたいと思います。

環境

今回の環境はこちら。

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

テスト対象の準備

まずはテスト対象のプログラムを作成していきます。

Mavenの設定など。

    <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>
        </plugins>
    </build>
</project>

テスト対象のクラス。

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));
    }
}

今回は内容ではなく数に着目するので、ほぼ同じ内容でクラスを増やします。

$ 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

増やした分は同じ数字のテスト対象クラス、テストクラスがペアになっています。

src/test/java/org/littlewings/surefire/CalcService2Test.java

package org.littlewings.surefire;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalcService2Test {
    @Test
    void plus() {
        CalcService2 sut = new CalcService2();
        assertEquals(5, sut.plus(3, 2));
    }

    @Test
    void minus() {
        CalcService2 sut = new CalcService2();
        assertEquals(2, sut.minus(8, 6));
    }
}

テストを実行。

$ mvn test

全部のテストが実行されます。

[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.053 s -- in org.littlewings.surefire.CalcService3Test
[INFO] Running org.littlewings.surefire.CalcService9Test
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.006 s -- in org.littlewings.surefire.CalcService9Test
[INFO] Running org.littlewings.surefire.CalcService5Test
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 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.006 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.006 s -- in org.littlewings.surefire.CalcService7Test
[INFO] Running org.littlewings.surefire.CalcService4Test
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 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.006 s -- in org.littlewings.surefire.CalcService8Test
[INFO] Running org.littlewings.surefire.CalcService1Test
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s -- in org.littlewings.surefire.CalcService1Test
[INFO] Running org.littlewings.surefire.CalcService6Test
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 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.006 s -- in org.littlewings.surefire.CalcService10Test
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 20, Failures: 0, Errors: 0, Skipped: 0
[INFO]

実行するテストクラスをファイルに分割する

では、includesFileに指定するテストクラスを振り分けていきます。

テストクラスはこれだけあります。

$ find src/test/java -name '*.java'
src/test/java/org/littlewings/surefire/CalcService1Test.java
src/test/java/org/littlewings/surefire/CalcService5Test.java
src/test/java/org/littlewings/surefire/CalcService7Test.java
src/test/java/org/littlewings/surefire/CalcService2Test.java
src/test/java/org/littlewings/surefire/CalcService3Test.java
src/test/java/org/littlewings/surefire/CalcService6Test.java
src/test/java/org/littlewings/surefire/CalcService10Test.java
src/test/java/org/littlewings/surefire/CalcService4Test.java
src/test/java/org/littlewings/surefire/CalcService9Test.java
src/test/java/org/littlewings/surefire/CalcService8Test.java

incluesFileに指定するにはsrc/test/java/の部分があると困るので、切り取ります。

$ find src/test/java -name '*.java' | perl -wp -e 's!src/test/java/!!'
org/littlewings/surefire/CalcService1Test.java
org/littlewings/surefire/CalcService5Test.java
org/littlewings/surefire/CalcService7Test.java
org/littlewings/surefire/CalcService2Test.java
org/littlewings/surefire/CalcService3Test.java
org/littlewings/surefire/CalcService6Test.java
org/littlewings/surefire/CalcService10Test.java
org/littlewings/surefire/CalcService4Test.java
org/littlewings/surefire/CalcService9Test.java
org/littlewings/surefire/CalcService8Test.java

これをファイルに書き出します。

$ find src/test/java -name '*.java' | perl -wp -e 's!src/test/java/!!' > test_classes.txt

テストクラスはクラス数で分割するよりもグループ数で分割することの方が多いような気がするので、数で割ってみましょう。

今回は3グループに分けます。

$ split -n 'l/3' -d test_classes.txt test_classes_group_

最後に指定しているのは分割後のファイルのprefixで、-dオプションを指定することで分割後のファイルのsuffixを数字(0始まり)にできます。

-nで指定のファイル数に分割できますが、l/Nの形式にしないと行の途中で切られたりします。

  -n, --number=CHUNKS     作成する出力ファイル数を CHUNKS 個にする。下記の説明を参照


塊 (CHUNKS) には以下を指定できます:
  N      入力サイズに基づいて N 個のファイルに分割する
  K/N     N 個中 K 番目を標準出力に出力する
  l/N     N 個のファイルに分割すが、行やレコード内の分割は行わない
  l/K/N   N 個中 K 番目を標準出力に出力するが、行やレコード内の分割は行わない
  r/N     'l' と同様だがラウンドロビン分割をする
  r/K/N   上記と同様だが N 個中 K 番目を標準出力に出力する

作成されたファイル。

$ ll test_classes_group_*
-rw-rw-r-- 1 xxxxx xxxxx 188 10月 26 16:35 test_classes_group_00
-rw-rw-r-- 1 xxxxx xxxxx 142 10月 26 16:35 test_classes_group_01
-rw-rw-r-- 1 xxxxx xxxxx 141 10月 26 16:35 test_classes_group_02

中身。

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

あとは実行するだけです。

0番目。

$ 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.064 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.007 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.008 s -- in org.littlewings.surefire.CalcService1Test
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
[INFO]

1番目。

$ 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.089 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.010 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.023 s -- in org.littlewings.surefire.CalcService10Test
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
[INFO]

2番目。

$ 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.066 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.008 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.007 s -- in org.littlewings.surefire.CalcService8Test
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
[INFO]

よいのではないでしょうか。

おわりに

Maven Surefire Pluginで、includesFileを使ってテストクラスを分割してファイルで指定、実行してみました。

あまり頻繁に使うものではないと思いますが、テストの実行でひと工夫したい時などに覚えておくとよいかなと思います。