2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JUnit6変更点メモ

2
Posted at

JUnit6 とは

  • Java のテスティングフレームワーク
  • 2025年9月30日に JUnit5 からメジャーバージョンが上がって ver 6.0.0 がリリースされた

JUnit5 との違い

6.0.0 のリリースノート を見た感じ、メジャーバージョンは上がってるけどそこまで大きな変更ではなさそう。
(4 から 5 へのバージョンアップは根本から完全に作り直されたので大きな変更だったが、 6 は Jupiter とかのベースの仕組みとかはそのままっぽい)

5 から 6 に上げることによる非互換の変更は、大まかに以下の通り。

  • Java の最低バージョンが 8 から 17 に上がった
  • いくつかの非互換な修正が入った

Java の最低バージョンが 8 から 17 に上がった

  • JUnit 5 は、 Java の最低バージョンが 8 だったが、 JUnit 6 からは 17 に上がっている
  • JUnit 6 に上げる場合は、 Java のバージョンも 17 以上に上げる必要がある

いくつかの非互換な修正が入った

JUnit Platform

  • サブコマンドを指定せずに ConsoleLauncher を実行する機能は削除
  • --h-h の代わり)や -help--help の代わり)といった非標準な ConsoleLauncher オプションのサポートは削除
  • JUnit 4 ベースの JUnitPlatform Runner を提供していた junit-platform-runner モジュールは削除
  • テスト検出・実行向けの Java Flight Recorder (JFR) 用イベントを提供していた junit-platform-jfr モジュールは削除され、 junit-platform-launcher モジュールに直接統合された
  • junit-platform-suite-commons モジュールは削除され、機能は junit-platform-suite モジュールに統合された
  • Maven Surefire / Failsafe 3.0.0 未満のバージョンはサポート終了
  • 以下の API は削除された
    • メソッド
      • ReflectionSupport.loadClass(String)
      • ReflectionUtils.readFieldValue(…​)
      • ReflectionUtils.getMethod(…​)
      • ConfigurationParameters.size()
      • MethodSelector.getMethodParameterTypes()
      • NestedMethodSelector.getMethodParameterTypes()
      • TestPlan.add(TestIdentifier)
        TestPlan.getChildren(String)
        TestPlan.getTestIdentifier(String)
      • EngineTestKit.execute(String, EngineDiscoveryRequest)
        EngineTestKit.execute(TestEngine, EngineDiscoveryRequest)
        EngineTestKit.Builder.filters(DiscoveryFilter…​)
    • クラス
      • BlacklistedExceptions
      • PreconditionViolationException
      • ClasspathScanningSupport
      • SingleTestExecutor
      • LegacyReportingUtils
    • コンストラクタ
      • ReportEntry()
      • LauncherDiscoveryRequestBuilder()
    • アノテーション
      • @UseTechnicalNames
  • 以前は junit.platform.reflection.search.useLegacySemantics システムプロパティで切り替え可能だった「レガシーなフィールド/メソッド探索ルール」のサポートは削除
    • 特定のフィールドまたはメソッドが Java 言語のルールに従って表示されるかオーバーライドされるかに関して、常に標準の Java セマンティクスに準拠するようになった
  • 以下のメソッドは 型境界が柔軟化され、nullable / non-nullable の両方を扱えるようになった
    • ConfigurationParameters.get(String, Function)
    • NamespacedHierarchicalStore.getOrComputeIfAbsent(N, K, Function)
    • NamespacedHierarchicalStore.getOrComputeIfAbsent(N, K, Function, Class)
  • junit-platform-suite-commons モジュールは削除され、その機能は junit-platform-suite モジュールに直接統合された
  • ConversionSupport は、非推奨の Locale(String) コンストラクターで使用される形式ではなく、Locale.forLanguageTag(String) ファクトリ メソッドでサポートされている IETF BCP 47 言語タグ形式を使用して、StringLocale に変換するようになった
  • NamespacedHierarchicalStore のすべての getOrComputeIfAbsent(…​) メソッドは非推奨となり、代わりに新しい computeIfAbsent(…​) メソッドが導入された
  • 次の構成パラメータのいずれかに無効な値を設定すると、テスト検出が失敗するようになった
    • junit.platform.discovery.issue.failure.phase
    • junit.platform.discovery.issue.severity.critical
  • ReflectionSupportfindNestedClasses(…​) および streamNestedClasses(…​) メソッドが返すネストされたクラスの順序について、決定論的だが意図的にわかりにくい並びで返すようになった
  • 実装を簡素化するために、TestIdentifier のシリアル化サポートが下位互換性のない方法で変更された

JUnit Jupiter

  • 次の非推奨 API は削除された
    • クラス
      • MethodOrderer.Alphanumeric
    • メソッド
      • InvocationInterceptor.interceptDynamicTest(Invocation, ExtensionContext)
  • 非推奨の構成パラメータ junit.jupiter.tempdir.scope はサポートされなくなった
  • JAVA_8 から JAVA_16 までの JRE 列挙定数は、JAVA_17 が新しいベースラインであるため実行時に使用できなくなり、非推奨になった
    • 以下は手修正が必要
      • @EnabledForJreRange および @DisabledForJreRangeminVersionmaxVersion
      • @EnabledOnJre および @DisabledForJreRangeversions
  • @EnabledForJreRange@DisabledForJreRange は、デフォルトの最小値が JAVA_17 となった
  • テスト メソッドとの一貫性を保つために、同じ包含クラスまたはインターフェースで宣言された @Nested クラスは、決定論的だが意図的にわかりにくい方法で順序付けられるようになった
  • junit-jupiter-migrationsupport モジュールとその含まれるクラスは非推奨となり、次のメジャーバージョンで削除される
  • @CsvSource および @CsvFileSource で使用しているライブラリを univocity-parsers から FastCSV に移行した結果、不正な CSV 入力に対してスローされる例外の根本原因とメッセージが変わる場合がある
    • 振る舞いは変わっていないが、発生した例外の型やメッセージでハンドリングしているカスタム処理がある場合は影響を受ける可能性がある
  • @CsvFileSourcelineSeparator 属性が削除された
    • 行区切り文字は自動検出されるようになり、\r\n\r\n のいずれも行区切り文字として扱われるようになった
  • @CsvSource および @CsvFileSourceignoreLeadingAndTrailingWhitespacenullValues などの属性が、通常のフィールドだけでなくヘッダーフィールドにも適用されるようになった
  • @CsvSource および @CsvFileSource では、閉じ引用符の後の余分な文字は許可されなくなった
    • 例えば、引用符として一重引用符が使用されている場合、次のCSV値 'foo’INVALID,'bar' は例外をスローするようになった
    • これにより、不正な入力が暗黙的に受け入れられたり、誤って解釈されたりすることがなくなった
  • 構成パラメータ junit.jupiter.params.arguments.conversion.locale.format のサポートが削除された
    • ロケール変換は、Locale.forLanguageTag(String) ファクトリ メソッドでサポートされている IETF BCP 47 言語タグ形式を使用して常に実行されるようになった
  • TestTemplateInvocationContextProvider インターフェースの provideTestTemplateInvocationContexts(ExtensionContext) メソッドの戻り値の型が Stream<TestTemplateInvocationContext> から Stream<? extends TestTemplateInvocationContext> に変更された
  • 次のメソッドの型境界は、より柔軟になり、nullable / non-nullable の両方を許可するように変更された
    • ExtensionContext.getConfigurationParameter(String, Function)
    • ExtensionContext.getOrComputeIfAbsent(K, Function)
    • ExtensionContext.getOrComputeIfAbsent(K, Function, Class)
  • ExtensionContext.Store のすべての getOrComputeIfAbsent(…​) メソッドは非推奨となり、代わりに新しい computeIfAbsent(…​) メソッドが導入された
  • 次の構成パラメータのいずれかに無効な値を設定すると、テストの検出または実行が失敗するようになった
    • junit.jupiter.execution.parallel.mode.default
    • junit.jupiter.execution.parallel.mode.classes.default
    • junit.jupiter.execution.timeout.mode
    • junit.jupiter.execution.timeout.thread.mode.default
    • junit.jupiter.extensions.testinstantiation.extensioncontextscope.default
    • junit.jupiter.tempdir.cleanup.mode.default
    • junit.jupiter.testinstance.lifecycle.default
  • Kotlin 固有の assertTimeout 関数の Executable パラメータのコントラクトが callsInPlace(executable, EXACTLY_ONCE) から callsInPlace(executable, AT_MOST_ONCE) に変更されたため、コンパイル エラーが発生する可能性がある

JUnit Vintage

  • JUnit Vintage テスト エンジンは現在非推奨となっており、少なくとも 1 つの JUnit 4 テスト クラスが見つかった場合は INFO レベルで警告を出力する
  • もともと Vintage エンジンは、移行時の一時的な利用が目的なので

JUnit 5 から 6 へのマイグレーション

5 から 6 への移行ガイドは以下。

Upgrading to JUnit 6.0 · junit-team/junit-framework Wiki · GitHub

ただ、上記ページの Overview にも書いてあるが、 6 に上げたからといって大幅な修正が必要になることはほとんどなさそう。

Migrating from JUnit 5.x.y to 6.0.0 should be much easier than migrating from 4.x to 5.x. Only APIs that have been deprecated for over two years have been removed. All other APIs continue to be supported, making version 6.0.0 a drop-in replacement in most cases.

(訳)
JUnit 5.x.yから6.0.0への移行は、4.xから5.xへの移行よりもはるかに簡単です。削除されたのは、2年以上前から非推奨となっているAPIのみです。その他のAPIは引き続きサポートされるため、ほとんどの場合、バージョン6.0.0はそのまま置き換え可能です。

上に非互換の変更を記載したが、基本的に普通に JUnit でテストを書いているだけだったら、あまり関わらなさそうな変更が多い印象。

非互換の変更一覧で心当たりのある部分があればピンポイントで対応しつつ、後はバージョン上げて動くか確かめるというよくある手順でのマイグレーションになると思われ(私見)。

機能追加

JUnit Jupiter に関して、 6.0.0 で新たに追加された機能について確認する。
リリースノートは この部分

  • 表示名(@DisplayName)に含まれる印刷できない制御文字は、代替表現に置き換えられた
    • \r > <CR>
    • \n > <LF>
    • その他 >
  • @TestClassOrder との一貫性を保つため、テストクラスで指定された @TestMethodOrder アノテーションは、 @Nested 内部クラスによって再帰的に継承されるようになった
  • 外側のクラスがそれぞれ @TestMethodOrder または @TestClassOrder を介して異なる順序付けを指定した場合に、 @Nested クラスとその @Nested 内部クラスのデフォルトの順序付けに戻すための新しい MethodOrderer.Default および ClassOrderer.Default を追加
  • @CsvSource および @CsvFileSource の実装は、メンテナンスが終了した univocity-parsers から FastCSV に移行された
    • これにより、CSV 入力処理の一貫性が向上し、特に不正なエントリの精度が向上し、エラーレポートと全体的なパフォーマンスが向上した
  • @ParameterizedClass および @ParameterizedTest の表示名では、引数の名前と値のペアが、 名前 = 値 の形式を使用して一貫してスタイル設定されるようになった
    • たとえば、 fruit=apple ではなく fruit = apple になる
  • パラメータ化テストの表示名に含まれるテキストベースの引数は、デフォルトで引用符で囲まれるようになった
    • なお、引用符で囲まれたテキスト内の特殊文字はエスケープされる
    • 詳細は ユーザーガイド を参照
  • パラメーター化されたテストのフォールバック文字列からオブジェクトへの変換では、単一の String 引数を受け入れるファクトリの既存のサポートに加えて、単一の CharSequence 引数を受け入れるファクトリメソッドとファクトリコンストラクターもサポートされるようになった
  • パラメーター化されたテストの Arguments インターフェースは、正式に @FunctionalInterface になった
  • Kotlin の suspend 修飾子をテストメソッドとライフサイクルメソッドに適用できるようになった
  • ExtensionContext.Store に新しい computeIfAbsent(…​) メソッドが追加され、null 非許容型の操作が簡素化された
  • ConditionEvaluationResult API に提供される理由文字列は、正式に @Nullable として宣言されるようになった

いくつか気になった部分について、実際に動きを見てみる。

表示名における制御文字の出力

実装
package sandbox.junit6;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

public class HelloWorldTest {

    @Test
    @DisplayName("Hello\r\nWorld\b")
    void helloWorld() {
        int result = 1 + 1;
        assertEquals(3, result);
    }
}

JUnit 5

image.png

JUnit 6

image.png

  • JUnit 5 のときはスペース?になって実質消えていたところが、新しい文字列に置き換わっている

ParameterizedTestの表示名

package sandbox.junit6;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;

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

public class HelloWorldTest {

    @ParameterizedTest
    @MethodSource
    void helloWorld(String text, int length) {
        assertEquals(length, text.length());
    }

    static List<Arguments> helloWorld() {
        return List.of(
            Arguments.arguments("hoge", 4),
            Arguments.arguments("  fuga  ", 8),
            Arguments.arguments("\"pi\r\nyo\"", 8)
        );
    }
}

JUnit 5

image.png

JUnit 6

image.png

  • パラメータが String だった場合にダブルクォーテーションで囲われるようになっている

Hello World(おまけ)

環境
> gradle --version
------------------------------------------------------------
Gradle 9.2.1
------------------------------------------------------------

Build time:    2025-11-17 13:40:48 UTC
Revision:      30ecdc708db275e8f8769ea0620f6dd919a58f76

Kotlin:        2.2.20
Groovy:        4.0.28
Ant:           Apache Ant(TM) version 1.10.15 compiled on August 25 2024
Launcher JVM:  25.0.1 (Eclipse Adoptium 25.0.1+8-LTS)
Daemon JVM:    C:\pf\java\adoptium\jdk-25.0.1+8 (no JDK specified, using current Java home)
OS:            Windows 11 10.0 amd64
build.gradle
plugins {
    id "java"
}

java {
    sourceCompatibility = "25"
    targetCompatibility = "25"
}

tasks.withType(JavaCompile).configureEach {
    options.encoding = "UTF-8"
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(platform("org.junit:junit-bom:6.0.2"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

test {
    useJUnitPlatform()
}
HelloWorldTest.java
package sandbox.junit6;

import org.junit.jupiter.api.Test;

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

public class HelloWorldTest {

    @Test
    void helloWorld() {
        int result = 1 + 1;
        assertEquals(3, result);
    }
}
実行結果
Expected :3
Actual   :2
<Click to see difference>

org.opentest4j.AssertionFailedError: expected: <3> but was: <2>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:158)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:139)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:201)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:152)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:147)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:558)
	at sandbox.junit6.HelloWorldTest.helloWorld(HelloWorldTest.java:12)
  • 基本的な実装方法については、特に JUnit 5 の頃と変わっている点はない

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?