1. 背景
JUnit 5 とは何ですか? まず、Java 単体テスト フレームワーク JUnit についてですが、JUnit ともう 1 つのフレームワーク TestNG が Java 分野の単体テスト フレームワークの主要な市場を占めていますが、その中でも JUnit は開発の歴史が長く、常に進化し続ける豊富な機能を持ち、ほとんどの Java 開発者に好まれており、読者にも好まれています。
JUnit の歴史について言えば、JUnit は 1997 年に誕生しました。初期バージョンは、2 人のプログラミング マスター、Kent Beck と Erich Gamma によって飛行機旅行中に完成されました。当時の Java テスト プロセスには成熟したツールが不足していたため、2 人は私たちは、より有用な Java テスト フレームワークを目指して、JUnit プロトタイプの設計と実装に協力しました。
20 年以上が経過した現在、JUnit はさまざまなバージョンを経て進化し、バージョン 5.x に発展し、JDK 8 以降のバージョンのサポート (Lambda のサポートなど) とより豊富なテスト フォーム (反復テスト、パラメーター化されたテストなど) を提供しています。 。
JUint を理解した後、JUnit 5 を振り返ってみましょう。このバージョンは、JUnit 単体テスト フレームワークのメジャー アップグレードと言えます。最初に Java 8 以降の実行環境が必要です。ただし、古いバージョンでコンパイルして実行することもできます。 JUnit 5 の機能、JDK 8 環境は必須です。
さらに、JUnit 5 は、3 つの異なるサブプロジェクトで構成されるいくつかの異なるモジュールに分割されている点で、以前のバージョンの JUnit とは異なります。
JUnit 5 = JUnitプラットフォーム+ JUnit Jupiter + JUnit Vintage
-
JUnit プラットフォーム: JVM 上でテスト フレームワークを開始するために使用される基本サービスで、コマンド ライン、IDE、ビルド ツールを介してテストを実行するためのサポートを提供します。
-
JUnit Jupiter : JUnit 5 の新しいプログラミング モデルと拡張モデルが含まれており、主にテスト コードと拡張コードを作成するために使用されます。
-
JUnit Vintage : JUnit 5 と互換性のある JUnit3.x および JUnit4.x テスト ケースを実行するために使用されます。
JUnit 5 が必要な理由
JUnit 5 とは何かについて話した後、次の質問について考えてみましょう。なぜ JUnit 5 が必要なのでしょうか?
JUnit のようなテスト フレームワークの出現以来、Java 単体テストの分野は徐々に成熟し、開発者は単体テスト フレームワークに対してより高い要件、つまりより多くのテスト方法と他のライブラリへの依存度の低下を求めるようになりました。
そこで、より強力なテスト フレームワークの誕生を誰もが期待しており、Java テスト分野のリーダーである JUnit は、JUnit 5 バージョンをリリースしました。その主な機能は次のとおりです。
-
新しいアサーションとテスト アノテーションを提供し、テスト
-
より豊富なテスト方法:動的テスト、反復テスト、パラメータ化されたテストなどをサポートします。
-
モジュール化を実装して、テスト実行やテスト検出などのさまざまなモジュールを分離し、依存関係を削減しました。
-
Lambda 式、Sream API などの Java 8 のサポートを提供します。
JUnit 5 の一般的な使用法の紹介
次に、JUnit 5 の使用法をすぐにマスターできるように、JUnit 5 の一般的な使用法をいくつか見てみましょう。
まず、JUnit 5 の依存関係座標を Maven プロジェクトに導入します (現在の JDK 環境は Java 8 以降である必要があることに注意してください)。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
最初のテストケース
JUnit 5 を導入すると、簡単なテスト ケースをすぐに作成して、このテスト ケースから JUnit 5 について学ぶことができます。
@DisplayName("我的第一个测试用例")
public class MyFirstTestCaseTest {
@BeforeAll
public static void init() {
System.out.println("初始化数据");
}
@AfterAll
public static void cleanup() {
System.out.println("清理数据");
}
@BeforeEach
public void tearup() {
System.out.println("当前测试方法开始");
}
@AfterEach
public void tearDown() {
System.out.println("当前测试方法结束");
}
@DisplayName("我的第一个测试")
@Test
void testFirstTest() {
System.out.println("我的第一个测试开始测试");
}
@DisplayName("我的第二个测试")
@Test
void testSecondTest() {
System.out.println("我的第二个测试开始测试");
}
}
このテスト ケースを直接実行すると、次のようにコンソール ログが表示されます。
左の列の結果に表示されるテスト項目名は、テスト クラスとメソッドで@DisplayNameを使用して設定した名前であることがわかります。このアノテーションは JUnit 5 によって導入され、テスト クラスを定義し、表示を指定するために使用されます。テスト レポート内のユース ケースの名前。このアノテーションはクラスとメソッドで使用できます。クラスで使用すると、そのクラスがテスト クラスであることを示し、メソッドで使用すると、そのメソッドがテスト メソッドであることを示します。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.0")
public @interface DisplayName {
String value();
}
サンプル コードで使用されているアノテーション ** @BeforeAll ** と@AfterAllのペアを見てみましょう。これらは、開始と終了の前にテスト クラス全体の操作を定義します。これらは静的メソッドのみを変更でき、主に実行中に使用されます。テストプロセス 必要なグローバルデータと外部リソースの初期化とクリーンアップ。これらとは異なり、 @BeforeEachおよび@AfterEachでマークされたメソッドは、各テスト ケース メソッドの前後に実行され、主にテスト ケースに必要な実行環境の準備と破棄を担当します。
テストプロセス中のこれらの基本的なアノテーションに加えて、さらに豊富で強力なアノテーションがあります。次に 1 つずつ学習してみましょう。
テストの実行を無効にする: @Disabled
テスト クラスの実行時に特定のテスト メソッドをスキップし、他のテスト ケースを通常どおり実行したい場合は、 @Disabled アノテーションを使用して、テスト メソッドが使用不可であり、テスト クラスのテスト メソッドの実行時にブロックされないことを示すことができます。 .JUnitの実行。
@Disbaled を使用した後の実行効果を見てみましょう。次のコードを元のテスト クラスに追加します。
@DisplayName("我的第三个测试")
@Disabled
@Test
void testThirdTest() {
System.out.println("我的第三个测试开始测试");
}
実行後、次のようにコンソール ログが表示されます。@Disabled でマークされたメソッドは実行されず、個々のメソッド情報のみが出力されます。
@Disabled をクラスで使用して、クラスの下にあるすべてのテスト メソッドを実行しないようにマークすることもできます。通常、複数のテスト クラスのテストを結合するときに使用されます。
埋め込みテストクラス: @Nested
作成するクラスとコードが徐々に増加するにつれて、対応するテスト クラスのテストが必要になります。テスト クラスの数が爆発的に増加するという問題を解決するために、JUnit 5 では、静的な内部メンバー クラスの形式でテスト ケース クラスを論理的にグループ化できる @Nested アノテーションが提供されています。また、各静的内部クラスは独自のライフサイクル メソッドを持つことができ、外部から内部へ階層順序で実行されます。さらに、ネストされたクラスを @DisplayName でマークして、正しいテスト名を使用できるようにすることもできます。簡単な使用法を見てみましょう。
@DisplayName("内嵌测试类")
public class NestUnitTest {
@BeforeEach
void init() {
System.out.println("测试方法执行前准备");
}
@Nested
@DisplayName("第一个内嵌测试类")
class FirstNestTest {
@Test
void test() {
System.out.println("第一个内嵌测试类执行测试");
}
}
@Nested
@DisplayName("第二个内嵌测试类")
class SecondNestTest {
@Test
void test() {
System.out.println("第二个内嵌测试类执行测试");
}
}
}
すべてのテスト ケースを実行すると、コンソールに次の結果が表示されます。
繰り返されるテスト: @RepeatedTest
JUnit 5 では、テスト メソッドの実行回数の設定が新たにサポートされ、テスト メソッドを繰り返し実行できるようになりました。テスト メソッドを N 回実行する場合は、次のコードに示すように @RepeatedTest でマークを付けることができます。
@DisplayName("重复测试")
@RepeatedTest(value = 3)
public void i_am_a_repeated_test() {
System.out.println("执行测试");
}
実行後、テスト メソッドは 3 回実行され、IDEA での実行結果は次の図のようになります。
name
これは基本的な使用法です。繰り返し実行されるテスト メソッドの名前を変更したり、@RepeatedTest によって提供される組み込み変数を使用して、その属性のプレースホルダーとして使用したりすることもできます。まず、使用法と効果を見てみましょう。
@RepeatedTest アノテーション内で使用される変数はcurrentRepetition
繰り返し回数を表し、totalRepetitions
変数は繰り返しの合計数を表し、displayName
変数はテスト メソッドの表示名を表します。これらの組み込み変数を直接使用して再定義できます。繰り返し実行されるときのテスト メソッドの名前。
新しい主張
アサーション API の設計に関して、JUnit 5 は大幅な改善を加え、Java 8 の新機能、特に Lambda 式を最大限に活用し、最終的に新しいアサーション クラス org.junit.jupiter.api.Assertions を提供しました。多くのアサーション メソッドは Lambda 式パラメータを受け入れます。アサーション メッセージで Lambda 式を使用する利点の 1 つは、ラムダ式が遅延評価されることです。メッセージの構築にコストがかかる場合、時間とリソースをある程度節約できます。
次のようにassertAll メソッドを使用して、メソッド内で複数のアサーションをグループ化することもできるようになりました。
@Test
void testGroupAssertions() {
int[] numbers = {0, 1, 2, 3, 4};
Assertions.assertAll("numbers",
() -> Assertions.assertEquals(numbers[1], 1),
() -> Assertions.assertEquals(numbers[3], 3),
() -> Assertions.assertEquals(numbers[4], 4)
);
}
グループ化されたアサーション内のいずれかのアサーションが失敗すると、MultipleFailuresError エラーがスローされます。
タイムアウト操作のテスト:assertTimeoutPreemptively
時間のかかるメソッドの実行時間をテストしたいが、テスト メソッドを無限に待機させたくない場合は、テスト メソッドに対してタイムアウト テストを実行できます。JUnit 5 では、このためのアサーション メソッドが導入され、タイムアウトに対する広範なサポートが提供されています。assertTimeout
。
テスト コードを 1 秒以内に実行したいとすると、次のテスト ケースを作成できます。
@Test
@DisplayName("超时方法测试")
void test_should_complete_in_one_second() {
Assertions.assertTimeoutPreemptively(Duration.of(1, ChronoUnit.SECONDS), () -> Thread.sleep(2000));
}
このテストは、コードの実行が 2 秒間スリープするため失敗しますが、テスト ケースは 1 秒で成功すると予想されます。ただし、スリープ時間を 1 秒に設定しても、テスト メソッドの実行中に、ターゲット コードに加えて、実行に時間がかかる追加のコードや命令が存在するため、テストが失敗することがあります。タイムアウトを時間パラメータの完全に正確な一致に制限する方法はありません。
例外テスト:assertThrows
コード内の例外のあるメソッドは、通常、try-catch を使用してキャプチャされ、処理されます。例外がスローされたコードをテストするために、JUnit 5 はテストするメソッドを提供します。最初のパラメータは例外の種類で、2 番目のパラメータは例外の種類です。それぞれはAssertions#assertThrows(Class<T>, Executable)
、 Runnable インターフェイスに似た関数インターフェイス パラメータです。パラメータは必要なく、戻り値もありません。ラムダ式の使用をサポートしています。具体的な使用法については、以下のコードを参照してください。
@Test
@DisplayName("测试捕获的异常")
void assertThrowsException() {
String str = null;
Assertions.assertThrows(IllegalArgumentException.class, () -> {
Integer.valueOf(str);
});
}
ラムダ式のコードで例外が発生すると、最初のパラメータの例外の種類と比較され、同じ種類の例外に属さない場合は、次のようなプロンプトがコンソールに出力されます。org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <IllegalArgumentException> but was: <...Exception>
JUnit 5 パラメータ化されたテスト
パラメーター化されたテストに JUnit 5 を使用するには、junit-jupiter-engine の基本的な依存関係に加えて、別のモジュール依存関係junit-jupiter-paramsも必要です。これは主にパラメーター化されたテストを作成するための API を提供します。同様に、同じバージョンの対応する依存関係を Maven プロジェクトに導入します。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
基本的なデータ ソース テスト: @ValueSource
@ValueSource は JUnit 5 が提供する最もシンプルなデータ パラメータ ソースです。Java の 8 つの基本型と文字列およびクラスをサポートしています。使用すると、アノテーションの対応する type 属性に割り当てられ、配列で渡されます。サンプル コードは次のとおりです:
public class ParameterizedUnitTest {
@ParameterizedTest
@ValueSource(ints = {2, 4, 8})
void testNumberShouldBeEven(int num) {
Assertions.assertEquals(0, num % 2);
}
@ParameterizedTest
@ValueSource(strings = {"Effective Java", "Code Complete", "Clean Code"})
void testPrintTitle(String title) {
System.out.println(title);
}
}
@ParameterizedTest は、パラメーター化されたテストに必要なアノテーションであり、@Test アノテーションに代わるものです。パラメータ化されたテスト メソッドには、このアノテーションを付ける必要があります。
テストを実行すると、結果は次の図のようになります。@ValueSource のパラメーターごとにターゲット メソッドが実行されます。パラメーターがテストの実行に失敗すると、テスト メソッドが失敗したことになります。
CSV データ ソース テスト: @CsvSource
@CsvSource を通じて、指定された CSV 形式 (カンマ区切り値) で一連のデータを挿入し、各カンマ区切り値を使用してテスト メソッドに対応するパラメーターと一致させることができます。使用例は次のとおりです。
@ParameterizedTest
@CsvSource({"1,One", "2,Two", "3,Three"})
void testDataFromCsv(long id, String name) {
System.out.printf("id: %d, name: %s", id, name);
}
実行結果は図のようになります。パラメータをカンマで区切るだけでなく、@CsvSource はカスタム シンボルもサポートしています。その を変更するだけですdelimiter
。デフォルトは です,
。
JUnit は、外部 CSV 形式のファイル データをデータ ソース実装として読み取る方法も提供します。@CsvFileSource を使用してリソース ファイル パスを指定するだけです。使い方は @CsvSource と同じくらい簡単なので、ここでは繰り返しません。
@CsvFileSource で指定されたリソース ファイル パスは、
/
現在のテスト リソース ディレクトリで始まり、そのディレクトリ内のファイルを検索する必要があります。
上記の 3 つのデータ ソース メソッドに加えて、JUnit は次の 3 つのデータ ソースも提供します。
- @EnumSource : 指定された Enum 列挙型にパラメータ値を渡して、列挙型に特定の値を構築できるようにします。
- @MethodSource : Stream/Array/Iterable オブジェクトをデータ ソースとして返すメソッドを指定します。このメソッドは静的である必要があり、パラメータを受け入れることができないことに注意してください。
- @ArgumentSource : ArgumentsProvider インターフェイスをデータ ソースとして実装するパラメーター クラスを使用します。その
provideArguments
メソッドをオーバーライドすると、カスタム タイプの Stream<Arguments> を返すことができ、テスト メソッドで必要なデータとして使用できます。
上記の 3 つのデータ ソース アノテーションに興味がある学生は、サンプル プロジェクトの ParameterizedUnitTest クラスを参照できます。ここでは 1 つずつ紹介しません。
結論
ここまでで、JUnit 5 の基本的な理解と習得が必要です。ソフトウェアの品質向上や研究開発の効率向上には単体テストが不可欠と言われています。JUnit 5 で単体テストを書けるようになることから始めて、JUnit 5 の習慣を身につけてください。テスト コードの作成: 継続的な練習を通じて開発効率を向上させ、作成したコードの品質保証を高めます。