Kotlin開発者のためのJUnit 5

1前書き

新しくリリースされたhttp://junit.org/junit5/[JUnit 5]は、Java用の有名なテストフレームワークの次期バージョンです。このバージョンには、特にJava 8で導入された機能を対象としたいくつかの機能が含まれています。

この簡単な記事では、同じツールがKotlin言語とどれだけうまく機能するかを説明します。

2簡単なJUnit 5のテスト

その最も簡単な方法では、Kotlinで書かれたJUnit 5テストは期待通りに動作します。テストクラスを書き、テストメソッドに @ Test アノテーションを付け、コードを書き、そしてアサーションを実行します。

class CalculatorTest {
    private val calculator = Calculator()

    @Test
    fun whenAdding1and3__thenAnswerIs4() {
        Assertions.assertEquals(4, calculator.add(1, 3))
    }
}

ここにあるものはすべて箱から出して動作します。標準の @ Test、@ BeforeAll、@ BeforeEach、@ AfterEach、 、および @ AfterAll 注釈を使用できます。テストクラスのフィールドとJavaとまったく同じように対話することもできます。

必要とされるインポートが異なることに注意してください、そして 私たちは Assert クラスの代わりに__Assertionsクラスを使って アサーションを行います** 。これはJUnit 5の標準的な変更であり、Kotlinに固有のものではありません。

3高度なアサーション

  • JUnit 5では、 ラムダ** を扱うための高度なアサーションをいくつか追加しています。

これらはKotlinでもJavaと同じように機能しますが、言語が機能する方法のため、少し異なる方法で表現する必要があります。

3.1. アサーション例外

JUnit 5では、呼び出しが例外をスローすると予想される場合についてのアサーションが追加されています。 ** メソッド内の単なる呼び出しではなく、特定の呼び出しが予想される例外をスローすることをテストできます。

Javaでは、 Assertions.assertThrows の呼び出しにラムダを渡します。

Kotlinでも同じことができますが、アサーション呼び出しの最後にブロックを追加することで、コードを読みやすくすることができます。

@Test
fun whenDividingBy0__thenErrorOccurs() {
    val exception = Assertions.assertThrows(DivideByZeroException::class.java) {
        calculator.divide(5, 0)
    }

    Assertions.assertEquals(5, exception.numerator)
}

このコードは、同等のJavaとまったく同じように機能しますが、読みやすくなっています。

3.2. 複数のアサーション

JUnit 5には、 同時に複数のアサーションを実行する 機能が追加されており、それらすべてを評価してすべての失敗について報告します。

これにより、1つのエラーを修正して次のエラーに達するのではなく、1回のテストでより多くの情報を収集できます。そのためには、任意の数のラムダを渡して Assertions.assertAll を呼び出します。

  • Kotlin では、これを少し異なる方法で処理する必要があります。 関数は実際には Executable ** 型の varargs パラメータを取ります。

現時点では、ラムダを関数型インターフェイスに自動的にキャストすることはサポートされていません。そのため、手動で行う必要があります。

fun whenSquaringNumbers__thenCorrectAnswerGiven() {
    Assertions.assertAll(
        Executable { Assertions.assertEquals(1, calculator.square(1)) },
        Executable { Assertions.assertEquals(4, calculator.square(2)) },
        Executable { Assertions.assertEquals(9, calculator.square(3)) }
    )
}

3.3. 真偽テストのサプライヤー

時々、ある呼び出しが true または false 値を返すことをテストしたいです。歴史的には、この値を計算し、必要に応じて assertTrue または assertFalse を呼び出していました。 JUnit 5では、チェックされている値を返す代わりにラムダを提供することができます。

  • Kotlinでは、例外をテストするために上で見たのと同じ方法で、ラムダ を渡すことができます。 メソッド参照を渡すこともできます** 。これは、ここで List.isEmpty を使用して行うように、既存のオブジェクトの戻り値をテストするときに特に便利です。

@Test
fun whenEmptyList__thenListIsEmpty() {
    val list = listOf<String>()
    Assertions.assertTrue(list::isEmpty)
}

3.4. 失敗メッセージのサプライヤ

場合によっては、アサーションの失敗時にデフォルトのエラーメッセージではなく表示されるように 独自のエラーメッセージを 提供する必要があります。

多くの場合、これらは単純な文字列ですが、** 計算にコストがかかる文字列を使用したい場合があります。 JUnit 5では、この文字列を計算するためのラムダを提供することができます。これは、事前に計算されるのではなく、失敗した場合にのみ呼び出されます。

これは、テストの実行速度を上げ、ビルド時間を短縮するのに役立ちます。これは以前に見たのとまったく同じように機能します。

@Test
fun when3equals4__thenTestFails() {
    val actual = someComputedValue()
    Assertions.assertEquals(3, actual) {
        "3 does not equal $actual"
    }
}

4データ駆動型テスト

JUnit 5の大きな改善点の1つは、 データ駆動型テストのネイティブサポート です。これらはKotlin ** でも同様に機能し、コレクションで関数マッピングを使用することで、テストを読みやすくし、保守しやすくなります。

4.1. TestFactoryメソッド

データ駆動テストを処理する最も簡単な方法は、 @ TestFactory アノテーションを使用することです。これは @ Test アノテーションを置き換えます、そして、メソッドは DynamicNode インスタンスの何らかのコレクションを返します - 通常は DynamicTest.dynamicTest を呼び出すことによって作成されます。

これはKotlinでもまったく同じように機能します。前に見たように、もう一度** クリーンな方法でラムダを渡すことができます。

@TestFactory
fun testSquares() = listOf(
    DynamicTest.dynamicTest("when I calculate 1^2 then I get 1") { Assertions.assertEquals(1,calculator.square(1))},
    DynamicTest.dynamicTest("when I calculate 2^2 then I get 4") { Assertions.assertEquals(4,calculator.square(2))},
    DynamicTest.dynamicTest("when I calculate 3^2 then I get 9") { Assertions.assertEquals(9,calculator.square(3))}
)

私たちはこれよりもうまくやることができます。単純な入力データリストに関数マッピングを実行することで、簡単にリストを作成できます。

@TestFactory
fun testSquares() = listOf(
    1 to 1,
    2 to 4,
    3 to 9,
    4 to 16,
    5 to 25)
    .map { (input, expected) ->
        DynamicTest.dynamicTest("when I calculate $input^2 then I get $expected") {
            Assertions.assertEquals(expected, calculator.square(input))
        }
    }

すぐに入力リストにテストケースを簡単に追加でき、自動的にテストが追加されます。

入力リストをクラスフィールドとして作成し、それを複数のテストで共有することもできます。

private val squaresTestData = listOf(
    1 to 1,
    2 to 4,
    3 to 9,
    4 to 16,
    5 to 25)
@TestFactory
fun testSquares() = squaresTestData
    .map { (input, expected) ->
        DynamicTest.dynamicTest("when I calculate $input^2 then I get $expected") {
            Assertions.assertEquals(expected, calculator.square(input))
        }
    }
@TestFactory
fun testSquareRoots() = squaresTestData
    .map { (expected, input) ->
        DynamicTest.dynamicTest("when I calculate the square root of $input then I get $expected") {
            Assertions.assertEquals(expected, calculator.squareRoot(input))
        }
    }

4.2. パラメータ化テスト

パラメータ化されたテストを書くためのより簡単な方法を可能にするためのJUnit 5への** 実験的な拡張があります。これらは org.junit.jupiter:junit-jupiter-params 依存関係からの @ ParameterizedTest アノテーションを使用して行われます。

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.0.0</version>
</dependency>

最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.junit.jupiter%22%20AND%20a%3A%22junit-jupiter-params%にあります。 22[メイヴン中央]。

@ MethodSource アノテーションを使用すると、テストと同じクラスにある静的関数を呼び出して** テストパラメータを作成できます。

これは可能ですが、Kotlinでは明らかではありません。 companionオブジェクト内で @ JvmStatic__アノテーションを使用する必要があります。

@ParameterizedTest
@MethodSource("squares")
fun testSquares(input: Int, expected: Int) {
    Assertions.assertEquals(expected, input **  input)
}

companion object {
    @JvmStatic
    fun squares() = listOf(
        Arguments.of(1, 1),
        Arguments.of(2, 4)
    )
}

これはまた、パラメータを生成するために使用されるメソッドがすべて一緒になければならないことを意味します。

パラメータ化テストを使用する他の方法はすべて、KotlinでもJavaの場合とまったく同じように機能します。 @ CsvSource は、ここで特別な注意を払うものです。私たちのテストをもっと読みやすくするために、ほとんどの場合、単純なテストデータに対して @ MethodSource の代わりにそれを使用できるからです。

@ParameterizedTest
@CsvSource(
    "1, 1",
    "2, 4",
    "3, 9"
)
fun testSquares(input: Int, expected: Int) {
    Assertions.assertEquals(expected, input **  input)
}

5タグ付きテスト

Kotlin言語は現在、クラスやメソッドに繰り返し注釈を付けることはできません。タグを @ Tags アノテーションで囲む必要があるため、これによってタグの使用がやや冗長になります。

@Tags(
    Tag("slow"),
    Tag("logarithms")
)
@Test
fun whenIcalculateLog2Of8__thenIget3() {
    assertEquals(3, calculator.log(2, 8))
}

これはJava 7でも必要とされ、すでにJUnit 5で完全にサポートされています。

6. 概要

JUnit 5には、私たちが使える強力なテストツールがいくつか追加されています。これらのほとんどすべてがKotlin言語で完璧に機能しますが、場合によっては、Javaで同等のものとはわずかに異なる構文が使用されます。

ただし、多くの場合、これらの構文の変更は、Kotlinを使用すると読みやすくなり、作業しやすくなります。

これらすべての機能の例はhttps://github.com/eugenp/tutorials/tree/master/core-kotlin[GitHubに追加]をご覧ください。