JUnit 5チュートリアル、パート1:JUnit 5、Mockito、およびHamcrestを使用したユニットテスト

JUnit 5は、Javaでユニットテストを開発するための新しいデファクトスタンダードです。この最新バージョンは、Java 5の制約を残し、Java 8の多くの機能を統合しました。特に、ラムダ式のサポートです。

JUnit 5の2部構成の紹介のこの前半では、JUnit 5でのテストを開始します。JUnit5を使用するようにMavenプロジェクトを構成する方法@Testと、@ParameterizedTestアノテーションを使用してテストを作成する方法を示します。また、JUnit 5で新しいライフサイクルアノテーションを操作する方法についても説明します。また、フィルタータグの使用例を示し、JUnit 5をサードパーティのアサーションライブラリ(この場合はHamcrest)と統合する方法を示します。 。最後に、JUnit 5をMockitoと統合するための簡単なチュートリアルの概要を説明します。これにより、複雑な実世界のシステムに対してより堅牢な単体テストを作成できます。

ダウンロードコードを入手するこのチュートリアルの例のソースコードを入手してください。JavaWorldのためにStevenHainesによって作成されました。

テスト駆動開発

Javaコードを一定期間開発している場合は、おそらくテスト駆動開発に精通しているので、このセクションでは簡単に説明します。ただし、単体テストを作成する理由と、開発者が単体テストを設計するときに採用する戦略を理解することが重要です。

テスト駆動開発(TDD)は、コーディング、テスト、および設計を織り交ぜたソフトウェア開発プロセスです。これは、アプリケーションの品質を向上させることを目的としたテストファーストのアプローチです。テスト駆動開発は、次のライフサイクルによって定義されます。

  1. テストを追加します。
  2. すべてのテストを実行し、新しいテストが失敗するのを観察します。
  3. コードを実装します。
  4. すべてのテストを実行し、新しいテストが成功することを確認します。
  5. コードをリファクタリングします。

図1は、このTDDライフサイクルを示しています。

スティーブンヘインズ

コードを書く前にテストを書くことには2つの目的があります。まず、解決しようとしているビジネス上の問題について考える必要があります。たとえば、成功したシナリオはどのように動作する必要がありますか?どのような条件が失敗する必要がありますか?彼らはどのように失敗する必要がありますか?次に、最初にテストを行うと、テストの信頼性が高まります。コードを書いた後にテストを書くときはいつでも、実際にエラーをキャッチしていることを確認するために、常にテストを中断する必要があります。最初にテストを作成すると、この余分な手順を回避できます。

ハッピーパスのテストを書くのは通常簡単です。良い入力があれば、クラスは決定論的な応答を返すはずです。ただし、特に複雑なコンポーネントの場合、ネガティブ(または失敗)テストケースの記述はより複雑になる可能性があります。

例として、データベースリポジトリのテストを作成することを検討してください。ハッピーパスでは、データベースにレコードを挿入し、生成されたキーを含む、作成されたオブジェクトを受け取ります。実際には、別のレコードによってすでに保持されている一意の列値を持つレコードを挿入するなど、競合の可能性も考慮する必要があります。さらに、おそらくユーザー名またはパスワードが変更されたために、リポジトリがデータベースに接続できない場合はどうなりますか?転送中にネットワークエラーが発生した場合はどうなりますか?定義したタイムアウト制限内にリクエストが完了しない場合はどうなりますか?

堅牢なコンポーネントを構築するには、ありそうなシナリオとありそうもないシナリオをすべて検討し、それらのテストを開発して、それらのテストを満たすコードを作成する必要があります。この記事の後半では、さまざまな障害シナリオを作成するための戦略と、それらのシナリオのテストに役立つJUnit5の新機能のいくつかについて説明します。

JUnit5の採用

JUnitをしばらく使用している場合、JUnit5での変更の一部は調整されます。2つのバージョンの違いの概要は次のとおりです。

  • JUnit 5がorg.junit.jupiterグループにパッケージ化され、MavenおよびGradleプロジェクトに含める方法が変更されました。
  • JUnit 4には、JDK5の最小JDKが必要でした。JUnit5には最低でもJDK8が必要です。
  • JUnitの4つの者は@Before@BeforeClass@After、と@AfterClass注釈を置き換えられている@BeforeEach@BeforeAll@AfterEach、と@AfterAll、それぞれ。
  • JUnit4の@Ignore注釈は注釈に置き換えられました@Disabled
  • @Category注釈は置き換えられている@Tag注釈。
  • JUnit 5は、新しいアサーションメソッドのセットを追加します。
  • ランナーは拡張機能に置き換えられ、拡張機能の実装者向けの新しいAPIが追加されました。
  • JUnit 5では、テストの実行を停止する前提条件が導入されています。
  • JUnit 5は、ネストされた動的テストクラスをサポートします。

この記事では、これらの新機能のほとんどについて説明します。

JUnit5を使用したユニットテスト

ユニットテストにJUnit5を使用するようにプロジェクトを構成するエンドツーエンドの例から、簡単に始めましょう。リスト1は、MathToolsメソッドが分子と分母をに変換するクラスを示していdoubleます。

リスト1.JUnit 5プロジェクトの例(MathTools.java)

 package com.javaworld.geekcap.math; public class MathTools { public static double convertToDecimal(int numerator, int denominator) { if (denominator == 0) { throw new IllegalArgumentException("Denominator must not be 0"); } return (double)numerator / (double)denominator; } }

MathToolsクラスとそのメソッドをテストするための2つの主要なシナリオがあります。

  • 有効なテスト我々は分子と分母のための非ゼロの整数を通過するには、。
  • 障害シナリオは、ここで我々は分母のゼロ値を渡します。

リスト2は、これら2つのシナリオをテストするためのJUnit5テスト・クラスを示しています。

リスト2.JUnit 5テスト・クラス(MathToolsTest.java)

 package com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class MathToolsTest { @Test void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.75, result); } @Test void testConvertToDecimalInvalidDenominator() { Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0)); } }

リスト2では、testConvertToDecimalInvalidDenominatorメソッドは呼び出しMathTools::convertToDecimal内でメソッドを実行しassertThrowsます。最初の引数は、スローされると予想される例外のタイプです。2番目の引数は、その例外をスローする関数です。このassertThrowsメソッドは関数を実行し、予期されたタイプの例外がスローされたことを検証します。

アサーションクラスとそのメソッド

The org.junit.jupiter.api.Test annotation denotes a test method. Note that the @Test annotation now comes from the JUnit 5 Jupiter API package instead of JUnit 4's org.junit package. The testConvertToDecimalSuccess method first executes the MathTools::convertToDecimal method with a numerator of 3 and a denominator of 4, then asserts that the result is equal to 0.75. The org.junit.jupiter.api.Assertions class provides a set of static methods for comparing actual and expected results. The Assertions class has the following methods, which cover most of the primitive data types:

  • assertArrayEquals compares the contents of an actual array to an expected array.
  • assertEquals compares an actual value to an expected value.
  • assertNotEquals compares two values to validate that they are not equal.
  • assertTrue validates that the provided value is true.
  • assertFalse validates that the provided value is false.
  • assertLinesMatch compares two lists of Strings.
  • assertNull validates that the provided value is null.
  • assertNotNull validates that the provided value is not null.
  • assertSame validates that two values reference the same object.
  • assertNotSame validates that two values do not reference the same object.
  • assertThrows validates that the execution of a method throws an expected exception (you can see this in the testConvertToDecimalInvalidDenominator example above).
  • assertTimeout validates that a supplied function completes within a specified timeout.
  • assertTimeoutPreemptively validates that a supplied function completes within a specified timeout, but once the timeout is reached it kills the function's execution.

If any of these assertion methods fail, the unit test is marked as failed. That failure notice will be written to the screen when you run the test, then saved in a report file.

Using delta with assertEquals

When using float and double values in an assertEquals, you can also specify a delta that represents a threshold of difference between the two. In our example we could have added a delta of 0.001, in case 0.75 was actually returned as 0.750001.

Analyzing your test results

In addition to validating a value or behavior, the assert methods can also accept a textual description of the error, which can help you diagnose failures. For example:

 Assertions.assertEquals(0.75, result, "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Assertions.assertEquals(0.75, result, () -> "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); 

The output will show the expected value of 0.75 and the actual value. It will also display the specified message, which can help you understand the context of the error. The difference between the two variations is that the first one always creates the message, even if it is not displayed, whereas the second one only constructs the message if the assertion fails. In this case, the construction of the message is trivial, so it doesn't really matter. Still, there is no need to construct an error message for a test that passes, so it's usually a best practice to use the second style.

Finally, if you're using an IDE like IntelliJ to run your tests, each test method will be displayed by its method name. This is fine if your method names are readable, but you can also add a @DisplayName annotation to your test methods to better identify the tests:

@Test @DisplayName("Test successful decimal conversion") void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); }

Running your unit test

In order to run JUnit 5 tests from a Maven project, you need to include the maven-surefire-plugin in the Maven pom.xml file and add a new dependency. Listing 3 shows the pom.xml file for this project.

Listing 3. Maven pom.xml for an example JUnit 5 project

  4.0.0 com.javaworld.geekcap junit5 jar 1.0-SNAPSHOT    org.apache.maven.plugins maven-compiler-plugin 3.8.1  8 8    org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4    junit5 //maven.apache.org   org.junit.jupiter junit-jupiter 5.6.0 test   

JUnit 5 dependencies

JUnit 5 packages its components in the org.junit.jupiter group and we need to add the junit-jupiter artifact, which is an aggregator artifact that imports the following dependencies:

  • junit-jupiter-api defines the API for writing tests and extensions.
  • junit-jupiter-engine 単体テストを実行するテストエンジンの実装です。
  • junit-jupiter-params パラメータ化されたテストのサポートを提供します。

次に、maven-surefire-pluginテストを実行するためにビルドプラグインを追加する必要があります。

最後に、maven-compiler-pluginラムダなどのJava 8機能を使用できるように、Java8以降のバージョンにを含めるようにしてください。

それを実行します!

次のコマンドを使用して、IDEまたはMavenからテストクラスを実行します。

mvn clean test

成功すると、次のような出力が表示されます。

 [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.math.MathToolsTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.832 s [INFO] Finished at: 2020-02-16T08:21:15-05:00 [INFO] ------------------------------------------------------------------------