Kotlin上のJUnit 5を使用したテスト

この記事では、JUnit 5プラットフォームの主な機能について説明し、Kotlinでの使用例を示します。 この資料は、KotlinやJUnitの初心者を対象としていますが、経験豊富な開発者は興味深いものを見つけるでしょう。

公式ユーザーガイド
この記事のテストのソースコード: GitHub

最初のテストを作成する前に、pom.xmlで依存関係を指定します。

<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.0.2</version> <scope>test</scope> </dependency> 

最初のテストを作成します。
 import org.junit.jupiter.api.Test class HelloJunit5Test { @Test fun `First test`() { print("Hello, JUnit5!") } } 

テストは成功です:

画像

JUnit 5の主な機能とさまざまな技術的なニュアンスの概要に移りましょう。

テストの表示名


@DisplayNameアノテーションの値、 @DisplayName Kotlin関数の名前で、テストの読み取り可能な表示名に加えて、特殊文字と絵文字を指定できます。

 import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test class HelloJunit5Test { @DisplayName("\uD83D\uDC4D") @Test fun `First test ╯°□°)╯`() { print("Hello, JUnit5!") } } 

ご覧のとおり、アノテーション値は関数名よりも優先されます。

画像

注釈はクラスに適用されます:

 @DisplayName("Override class name") class HelloJunit5Test { 

画像

アサーション


アサーションはクラスorg.junit.jupiter.Assertionsあり、静的メソッドです。

基本的なアサーション


JUnitには、期待値と実際の値をチェックするためのいくつかのオプションが含まれています。 それらの1つでは、最後の引数はエラーの場合に表示されるメッセージであり、もう1つでは、テストが失敗した場合にのみ文字列の値を計算できるSupplier機能インターフェースを実装するラムダ式です。

 import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class HelloJunit5Test { @Test fun `Base assertions`() { assertEquals("a", "a") assertEquals(2, 1 + 1, "Optional message") assertEquals(2, 1 + 1, { "Assertion message " + "can be lazily evaluated" }) } } 

グループアサーション


グループアサーションをテストするには、最初に2つのプロパティを持つPersonクラスを作成します。

 class Person(val firstName: String, val lastName: String) 

両方のアサーションが実行されます:

 import org.junit.jupiter.api.Assertions.assertAll import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.function.Executable class HelloJunit5Test { @Test fun `Grouped assertions`() { val person = Person("John", "Doe") assertAll("person", Executable { assertEquals("John", person.firstName) }, Executable { assertEquals("Doe", person.lastName) } ) } } 

true / falseのチェックでラムダとメソッド参照を渡す


  @Test fun `Test assertTrue with reference and lambda`() { val list = listOf("") assertTrue(list::isNotEmpty) assertTrue { !list.contains("a") } } 

例外


例外のあるJUnit 4よりも透明な例外:

  @Test fun `Test exception`() { val exception: Exception = assertThrows(IllegalArgumentException::class.java, { throw IllegalArgumentException("exception message") }) assertEquals("exception message", exception.message) } 

ランタイム検証のテスト


残りの例のように、すべてが単純に行われます:

  @Test fun `Timeout not exceeded`() { //     -,    1000  assertTimeout(ofMillis(1000)) { print(" ,     1 ") Thread.sleep(3) } } 

この場合、実行時間が既に許容値を超えていても、ラムダ式は完全に実行されます。 割り当てられた時間が経過した直後にテストをクラッシュさせるには、 assertTimeoutPreemptivelyメソッドを使用する必要があります。

  @Test fun `Timeout not exceeded with preemptively exit`() { //  ,      1000  assertTimeoutPreemptively(ofMillis(1000)) { print(" ,     1 ") Thread.sleep(3) } } 

外部アサーションライブラリ


一部のライブラリは、アサーションを使用するJUnitよりも強力で表現力のある手段を提供します。 特に、Hamcrestは、とりわけ、配列とコレクションをチェックするための多くの機能を提供します。 いくつかの例:

 import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.containsInAnyOrder import org.hamcrest.Matchers.greaterThanOrEqualTo import org.hamcrest.Matchers.hasItem import org.hamcrest.Matchers.notNullValue import org.junit.jupiter.api.Test class HamcrestExample { @Test fun `Some examples`() { val list = listOf("s1", "s2", "s3") assertThat(list, containsInAnyOrder("s3", "s1", "s2")) assertThat(list, hasItem("s1")) assertThat(list.size, greaterThanOrEqualTo(3)) assertThat(list[0], notNullValue()) } } 

仮定


仮定は、特定の条件が満たされた場合にのみテストを実行する機能を提供します。

 import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.Test class AssumptionTest { @Test fun `Test Java 8 installed`() { assumeTrue(System.getProperty("java.version").startsWith("1.8")) print("Not too old version") } @Test fun `Test Java 7 installed`() { assumeTrue(System.getProperty("java.version").startsWith("1.7")) { "Assumption doesn't hold" } print("Need to update") } } 

同時に、失敗した仮定によるテストはクラッシュしませんが、中断されます:

画像

データ駆動テスト


JUnit 5の主な機能の1つは、データ駆動型テストのサポートです。

試験工場


テストを生成する前に、より明確にするために、 Personクラスをデータクラスにします。これは、とりわけtoString()メソッドをオーバーライドし、 birthDateおよびageプロパティを追加します。

 import java.time.LocalDate import java.time.Period data class Person(val firstName: String, val lastName: String, val birthDate: LocalDate?) { val age get() = Period.between(this.birthDate, LocalDate.now()).years } 

次の例では、各人の年齢が少なくとも特定の年齢であることを確認するためのテストパックを生成します。

 import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.DynamicTest.dynamicTest import org.junit.jupiter.api.TestFactory import java.time.LocalDate class TestFactoryExample { @TestFactory fun `Run multiple tests`(): Collection<DynamicTest> { val persons = listOf( Person("John", "Doe", LocalDate.of(1969, 5, 20)), Person("Jane", "Smith", LocalDate.of(1997, 11, 21)), Person("Ivan", "Ivanov", LocalDate.of(1994, 2, 12)) ) val minAgeFilter = 18 return persons.map { dynamicTest("Check person $it on age greater or equals $minAgeFilter") { assertTrue(it.age >= minAgeFilter) } }.toList() } } 

画像

DynamicTestコレクションに加えて、 DynamicTestアノテーションが付けられたメソッドで、 StreamIterableIterator返すことができます。

動的テストの実行ライフサイクルは、 @BeforeEachによって注釈が付けられたメソッドが各動的テストではなく@TestFactoryメソッドに対してのみ実行されるという点で、 @TestFactoryメソッドとは@TestFactoryます。 たとえば、次のコードを実行すると、 Reset some var関数は1回だけ呼び出され、 someVar変数を使用して確認できます。

  private var someVar: Int? = null @BeforeEach fun `Reset some var`() { someVar = 0 } @TestFactory fun `Test factory`(): Collection<DynamicTest> { val ints = 0..5 return ints.map { dynamicTest("Test №$it incrementing some var") { someVar = someVar?.inc() print(someVar) } }.toList() } 

画像

パラメータ化されたテスト


動的なテストのようなパラメータ化されたテストでは、1つのメソッドに基づいて一連のテストを作成できますが、 @TestFactoryは異なる方法でテストを作成します。 このメソッドの操作を説明するには、まずpom.xml依存関係を追加します。

  <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.0.2</version> <scope>test</scope> </dependency> 

受信日付がすでに過去であることを確認するテストのコード:

 class ParameterizedTestExample { @ParameterizedTest @ValueSource(strings = ["2002-01-23", "1956-03-14", "1503-07-19"]) fun `Check date in past`(date: LocalDate) { assertTrue(date.isBefore(LocalDate.now())) } } 

@ValueSource注釈@ValueSourceは、 intlongdoubleまたはString配列にすることができます。 上記の例からわかるように、文字列の配列の場合、可能であれば、入力パラメーターの型への暗黙的な変換が使用されます。 @ValueSource使用@ValueSourceと、テスト呼び出しごとに1つの入力パラメーターのみを渡すことができ@ValueSource

@EnumSourceにより、テストメソッドは列挙定数を受け入れることができます。

  @ParameterizedTest @EnumSource(TimeUnit::class) fun `Test enum`(timeUnit: TimeUnit) { assertNotNull(timeUnit) } 

特定の定数を残すか除外できます。

  @ParameterizedTest @EnumSource(TimeUnit::class, mode = EnumSource.Mode.EXCLUDE, names = ["SECONDS", "MINUTES"]) fun `Test enum without days and milliseconds`(timeUnit: TimeUnit) { print(timeUnit) } 

画像

データソースとして使用されるメソッドを指定することができます。

  @ParameterizedTest @MethodSource("intProvider") fun `Test with custom arguments provider`(argument: Int) { assertNotNull(argument) } companion object { @JvmStatic fun intProvider(): Stream<Int> = Stream.of(0, 42, 9000) } 

Javaコードでは、このメソッドは静的である必要があります@JvmStaticでは、コンパニオンオブジェクトで宣言し、 @JvmStatic注釈を付けることでこれを実現します。 非静的メソッドを使用するには、テストインスタンスのライフサイクルを変更する必要があります。より正確には、デフォルトで行われるメソッドごとに1つのインスタンスではなく、クラスごとにテストのインスタンスを1つ作成する必要があります。

 @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ParameterizedTestExample { @ParameterizedTest @MethodSource("intProvider") fun `Test with custom arguments provider`(argument: Int) { assertNotNull(argument) } fun intProvider(): Stream<Int> = Stream.of(0, 42, 9000) } 

繰り返し可能なテスト


テストの繰り返し回数は次のように示されます。

  @RepeatedTest(10) fun ` `() { } 

画像

テストの表示名をカスタマイズすることが可能です:

  @RepeatedTest(10, name = "{displayName} {currentRepetition}  {totalRepetitions}") fun ` `() { } 

画像

現在のテストと繰り返しテストのグループに関する情報には、対応するオブジェクトからアクセスできます。

  @RepeatedTest(5) fun `Repeated test with repetition info and test info`(repetitionInfo: RepetitionInfo, testInfo: TestInfo) { assertEquals(5, repetitionInfo.totalRepetitions) val testDisplayNameRegex = """repetition \d of 5""".toRegex() assertTrue(testInfo.displayName.matches(testDisplayNameRegex)) } 

ネストされたテスト


JUnit 5では、ネストされたテストを記述して可視性を高め、テスト間の関係を強調できます。 Personクラスとテスト用の独自の引数プロバイダーを使用して、 Personオブジェクトのストリームを返す例を作成してみましょう。

 class NestedTestExample { @Nested inner class `Check age of person` { @ParameterizedTest @ArgumentsSource(PersonProvider::class) fun `Check age greater or equals 18`(person: Person) { assertTrue(person.age >= 18) } @ParameterizedTest @ArgumentsSource(PersonProvider::class) fun `Check birth date is after 1950`(person: Person) { assertTrue(LocalDate.of(1950, 12, 31).isBefore(person.birthDate)) } } @Nested inner class `Check name of person` { @ParameterizedTest @ArgumentsSource(PersonProvider::class) fun `Check first name length is 4`(person: Person) { assertEquals(4, person.firstName.length) } } internal class PersonProvider : ArgumentsProvider { override fun provideArguments(context: ExtensionContext): Stream<out Arguments> = Stream.of( Person("John", "Doe", LocalDate.of(1969, 5, 20)), Person("Jane", "Smith", LocalDate.of(1997, 11, 21)), Person("Ivan", "Ivanov", LocalDate.of(1994, 2, 12)) ).map { Arguments.of(it) } } } 

結果は非常に明白です:

画像

おわりに


JUnit 5は非常に使いやすく、テストを記述するための多くの便利な機能を提供します。 Kotlinを使用したデータ駆動型テストは、開発の容易さと簡潔なコードを提供します。

よろしくお願いします!

Source: https://habr.com/ru/post/J346452/


All Articles