mvn test は成功します。終了コードは 0、赤くなる箇所もありません。実行されたテストだけが 0 件です。
[INFO] --- surefire:3.5.6:test (default-test) @ demo ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] BUILD SUCCESS
target/surefire-reports/ に TEST-*.xml は 1 つも作られません(実測で 0 個。ディレクトリ自体ができない)。
テストクラスのコンパイルは通っていて、target/test-classes/ には .class が置かれています。
例外も警告も出ないので、手がかりは 2 行しかありません。
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0… Surefire の集計Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider… どちらの実行系が選ばれたか(Surefire 2.22.2 はこの行を出しません。3.x で追加された)
CI のジョブは成功で終わります。カバレッジを測っている構成なら、そちらが 2 つ目の観測点になる——JaCoCo は
レポートを普通に生成し(jacoco.exec も作られる)、全カウンタが covered="0" の 0% として出ます。
直し方 1:まず緑に戻す(JUnit 4 のテストをそのまま走らせる)
junit-vintage-engine をテストスコープに足します。テストコードは 1 行も変えません。
Spring Boot の親 POM や junit-bom で依存を管理しているなら、<version> を書かずに既存の JUnit 系列へ
揃えるのが確実です(Boot 3.2.12 で実測。BOM が 5.10.5 を当てて 1 件走る)。
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
版を自分で書く場合は、Jupiter 側と同じ JUnit の版に揃えます。実測したのは揃えた組だけで (Jupiter 5.8.2 + vintage 5.8.2、Jupiter 6.1.2 + vintage 6.1.2)、食い違わせた構成は試していません。
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>6.1.2</version>
<scope>test</scope>
</dependency>
JUnit 6 系を書くなら前提が 2 つあります。 JUnit 6 は実行時に Java 17 以上を要求し、Vintage engine は JUnit 4.12 以上が classpath にあることを要求します(どちらも JUnit 6.1 のユーザーガイド)。Java 11 で動く サービスや、JUnit 4.11 以下を残しているプロジェクトは、6.1.2 をそのまま貼らずに、いま使っている JUnit 系列に 合う版を選ぶか、直し方 2 へ進みます。
これは恒久策ではありません。 Vintage engine は JUnit 6.0 で非推奨になっていて、ユーザーガイドは
「Jupiter か、JUnit Platform に対応した別のフレームワークへ移行するあいだ、一時的に使うもの」と位置づけています
(VintageTestEngine 自体に @Deprecated(since = "6.0") が付いています)。非推奨であって削除ではないので、
足せば今日のビルドは緑に戻ります。着地は次の節です。
直し方 2:テストを JUnit 5 へ移す(上流が案内する着地)
@Test の import と assert の出どころを Jupiter に替えます。JUnit 4 の依存は外せます。
package dev.errfix;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public final class LegacyTest {
@Test
public void shouldRun() {
assertEquals(2, 1 + 1);
}
}
pom.xml 側は Jupiter だけを残します。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.1.2</version>
<scope>test</scope>
</dependency>
こちらも JUnit 6 系を書くなら実行時に Java 17 以上が要ります。移す量が多いなら、1 を先に入れて緑に戻し、
それから 2 を進める順序になります。junit-vintage-engine は現行の JUnit(6.1.2)でも公開されているので、
その順序は取れます。ただし残したままにする前提では置きません——非推奨の bridge を実行経路として固定すると、
JUnit 4 を残した範囲と理由を、あとで改めて切り分けることになります。
0 件のまま成功させない(検出)
failIfNoTests を立てると、同じ状態がビルドの失敗になります。
mvn -B test -DfailIfNoTests=true
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.6:test (default-test) on project demo: No tests were executed! (Set -DfailIfNoTests=false to ignore this error.) -> [Help 1]
Surefire 2.22.2 と 3.5.6 の両方で終了コードが 1 になります。pom.xml に固定するなら次の形。
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.6</version>
<configuration>
<failIfNoTests>true</failIfNoTests>
</configuration>
</plugin>
これが止めるのは「1 件も走らなかった」だけです。100 件のうち 40 件が拾われなくなった状態は素通りするので、 そちらは実行件数そのものを見る必要があります。
なぜ JUnit 5 を足すと 0 件になるのか
Surefire は、テストのクラスパスに何があるかを見て実行系(provider)を自分で選びます。選択結果は
3.x なら Using auto detected provider の行に出ます。同じ JUnit 4 のテストで、依存だけを変えた実測です。
| テストの依存 | 選ばれた provider | 結果 |
|---|---|---|
| JUnit 4.13.2 だけ | org.apache.maven.surefire.junit4.JUnit4Provider | Tests run: 1 |
| JUnit 4.13.2 + Jupiter | org.apache.maven.surefire.junitplatform.JUnitPlatformProvider | Tests run: 0 |
| JUnit 4.13.2 + Jupiter + Vintage | JUnitPlatformProvider | Tests run: 1 |
JUnit Platform が選ばれると、テストを探すのは platform に登録された engine になります。Jupiter engine が
担当するのは Jupiter のプログラミングモデル(代表が org.junit.jupiter.api.Test。@ParameterizedTest や
@TestFactory も同じ engine です)で、org.junit.Test(JUnit 4)は担当外になります。JUnit 4 を担当する
engine が junit-vintage-engine で、それが classpath に無ければ、JUnit 4 のテストを探す主体が 1 つも存在
しない状態になります。探す側がいないので、失敗ではなく 0 件という結果になる。
この表は Surefire 3.5.x での実測です。 Surefire は 3.6.0 で provider を surefire-junit-platform の
1 つに統合し、JUnit 4 のテストも Vintage engine 経由で実行する方式へ変えています(3.6.0 の時点では
3.6.0-M1 がマイルストーンとして出ている段階で、本記事は安定版の最新である 3.5.6 で測っています)。
その系列では 1 行目の JUnit4Provider という経路自体が無くなるため、JUnit 4 だけの構成でも Vintage が
要ることになります。ここは未検証です。
引き金は JUnit 4 が古くなったことではありません。JUnit 5 を足したことです。上の表の 1 行目のとおり、 JUnit 4 のテストは JUnit 4 だけの構成なら今でも実行されます。
Spring Boot 2 から 3 へ上げたプロジェクトで起きる形
spring-boot-starter-test は Jupiter を連れてきます。Boot 3.2.12 の依存ツリーを実際に開くと、
org.junit.jupiter:junit-jupiter:5.10.5(api / params / engine)と junit-platform-engine:1.10.5 が入り、
vintage engine は 1 つも含まれません。
そこへ junit:junit を残したまま JUnit 4 のテストを走らせると、上と同じ結果になります。
[INFO] --- surefire:3.1.2:test (default-test) @ demo ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
junit-vintage-engine を版指定なしで足すと、同じ構成で Tests run: 1, ... -- in dev.errfix.LegacyTest に
戻ります(Boot の BOM が版を当てる)。
切り分け
- 一部のテストだけが消えている:
Tests run:が 0 ではなく、以前より小さい数で止まっている状態です。 JUnit 4 のクラスと Jupiter のクラスが混在していて、Jupiter 側だけが拾われています。数え方は同じで、 直し方も同じ(vintage を足すか、残りを移す)。 Tests run: 0だがテストクラスが 1 つも無い:src/test/javaが空、あるいはクラス名が Surefire の 既定パターン(*Test/Test*/*Tests/*TestCase)に一致していません。この記事の境界とは別です。- Gradle を使っている:Surefire の provider 自動切替とは経路が違い、Gradle は
useJUnit()/useJUnitPlatform()/useTestNG()でテストフレームワークを指定します。Platform を使うならuseJUnitPlatform()が要り、engine が足りない形の症状は起きえますが、本記事では検証していません。 Using auto detected providerの行が出ない:Surefire が 2.x です。3.x に上げるとこの行が出て、 どちらの実行系が選ばれたかが読めます(それだけでは 0 件は直りません。実測で 3.5.6 でもTests run: 0のまま)。- 同じ「ビルドは成功しているのに計測対象が減る」型として、 Lombok 更新で JaCoCo のカバレッジ母数が黙って縮む があります。あちらは実行件数ではなくカバレッジの母数が縮み、率はむしろ上がります。
検証環境
- image:
eclipse-temurin(digest 固定sha256:68868d04...= Temurin 25.0.3+9)、Maven 3.9.12、maven.compiler.release=17 - 再現:JUnit 4.13.2 の
@Testを持つテスト 1 本+junit-jupiter-api/junit-jupiter-engine5.8.2、 Surefire 2.22.2 でmvn clean test→ Maven は成功し、TEST-*.xmlが 0 個。判定スクリプトが レポート不在を確認して終了コード 1 を返す(終了コード 1 は判定スクリプトのもので、Maven は 0) - 修正:同じ Surefire・同じ Maven で、テストを Jupiter の
@Test/Assertionsへ移す →TEST-*.xmlにtests="1"が入り、判定スクリプトが終了コード 0 - provider の表は、同じテストソースのまま依存だけを差し替えて Surefire 3.5.6 で採取(
Using auto detected providerの行とTests run:の値) failIfNoTests=trueは Surefire 2.22.2 と 3.5.6 の両方で終了コード 1 を確認- カバレッジ側の 0% は、同じ再現構成に jacoco-maven-plugin 0.8.15(
prepare-agent+report)を足して確認。jacoco.execは作られ、jacoco.xmlの report レベルはINSTRUCTION missed="7" covered="0"など 全カウンタが被覆 0 - vintage を足す側は 2 通り実測:Jupiter 5.8.2 + vintage 5.8.2(Surefire 2.22.2)と、 Jupiter 6.1.2 + vintage 6.1.2(Surefire 3.5.6)
- Spring Boot の節は
spring-boot-starter-parent3.2.12 で実測(dependency:treeの中身、 Boot が固定する Surefire 3.1.2、vintage 追加前後のTests run:) - 本文がコピペ用に指しているのは Maven Central の現行(JUnit 6.1.2、maven-surefire-plugin 3.5.6 = 安定版の最新。3.6.0-M1 はマイルストーン)。ケースが検証した 5.8.2 / 2.22.2 は境界ではなく、 旧構成のまま再現することを示す点として残している
- Vintage engine の状態(JUnit 6.0 で非推奨・移行期間の一時利用)、JUnit 6 の実行時 Java 17 以上、 Vintage の JUnit 4.12 以上という前提は、JUnit 6.1 のユーザーガイドで確認した。Surefire 3.6.0 の provider 統合は Surefire の Selecting Providers ドキュメントによる(いずれも本記事では未実測)
- 引用したログのモジュール名は
demoに短縮している
再現から修正までは errfix の検証ハーネスが機械的に確認しています。provider の自動選択と Tests run: の値は
上記のとおり実行して採取したもので、junit-vintage-engine が現行版でも公開されていることは Maven Central の
maven-metadata.xml で確認しました。