Kotlin開発者のためのJUnit 5

Kotlin開発者向けのJUnit 5

1. 前書き

新しくリリースされたJUnit 5は、Java用のよく知られたテストフレームワークの次のバージョンです。 このバージョンには、いくつかのfeatures that specifically target functionality introduced in Java 8が含まれています。これは、主にラムダ式の使用を中心に構築されています。

この簡単な記事では、同じツールworks with the Kotlin languageがどれだけうまくいくかを示します。

2. 単純なJUnit5テスト

最も簡単な場合、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とまったく同じように、テストクラスのフィールドとやり取りすることもできます。

必要なインポートは異なり、we doassertions using the Assertions class instead of the Assert classであることに注意してください。 これはJUnit 5の標準的な変更であり、Kotlinに固有のものではありません。

先に進む前に、テスト名を変更して、Kotlinでbacktick identifiersを使用しましょう。

@Test
fun `Adding 1 and 3 should be equal to 4`() {
    Assertions.assertEquals(4, calculator.add(1, 3))
}

今でははるかに読みやすくなっています! Kotlinでは、バッククォートを使用してすべての変数と関数を宣言できますが、通常のユースケースでは宣言しないことをお勧めします。

3. 高度なアサーション

JUnit 5 adds some advanced assertions forworking with lambdas。 これらはKotlinでもJavaと同じように動作しますが、言語の動作方法により、少し異なる方法で表現する必要があります。

3.1. 例外の主張

JUnit 5は、呼び出しが例外をスローすると予想される場合のアサーションを追加します。 We can test that a specific call — rather than just any call in the method — throws the expected exception.例外自体をアサートすることもできます。

Javaでは、ラムダをAssertions.assertThrowsの呼び出しに渡します。 Kotlinでも同じことを行いますが、アサーション呼び出しの最後にブロックを追加することでmake the code more readableを実行できます。

@Test
fun `Dividing by zero should throw the DivideByZeroException`() {
    val exception = Assertions.assertThrows(DivideByZeroException::class.java) {
        calculator.divide(5, 0)
    }

    Assertions.assertEquals(5, exception.numerator)
}

このコードworks exactly the same as the Java equivalent but is easier to readは、assertThrows関数を呼び出す括弧内にラムダを渡す必要がないためです。

3.2. 複数のアサーション

JUnit 5はperform multiple assertions at the same timeに機能を追加し、それらすべてを評価してすべての障害について報告します。

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

In Kotlin、これを少し異なる方法で処理する必要があります。 function actually takes a varargs parameter of type Executable

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

fun `The square of a number should be equal to that number multiplied in itself`() {
    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 allows us to pass in a lambdaは、例外をテストするために上記で見たのと同じ方法です。 We can also pass in method references。 これは、List.isEmptyを使用してここで行うように、既存のオブジェクトの戻り値をテストするときに特に役立ちます。

@Test
fun `isEmpty should return true for empty lists`() {
    val list = listOf()
    Assertions.assertTrue(list::isEmpty)
}

3.4. 故障メッセージのサプライヤー

場合によっては、デフォルトではなく、アサーションの失敗時にprovide our own error messageを表示したいことがあります。

多くの場合、これらは単純な文字列ですが、sometimes we may want to use a string that is expensive to computeです。 JUnit 5では、provide a lambda to compute this stringが可能であり、事前に計算される代わりにonly called on failureになります。

これはmake the tests run faster and reduce build timesに役立ちます。 これは、前に見たのとまったく同じように機能します。

@Test
fun `3 is equal to 4`() {
    Assertions.assertEquals(3, 4) {
        "Three does not equal four"
    }
}

4. データ駆動型テスト

JUnit 5の大きな改善点の1つは、native support for data-driven testsです。 これらのwork equally well in Kotlin、およびコレクションでの関数型マッピングの使用により、make our tests easier to read and maintain.が可能になります。

4.1. TestFactoryメソッド

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

これはKotlinでもまったく同じように機能し、前に見たように、pass in the lambda in a cleaner wayを再度実行できます。

@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))}
)

しかし、これよりもうまくいくことができます。 簡単にbuild our list by performing some functional mapping on a simple input list of data:できます

@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))
        }
    }

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

create the input list as a class fieldを実行して、複数のテスト間で共有することもできます。

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. パラメータ化されたテスト

パラメータ化されたテストを簡単に記述できるようにするためのexperimental extensions to JUnit 5があります。 これらは、org.junit.jupiter:junit-jupiter-params依存関係からの@ParameterizedTestアノテーションを使用して行われます。


    org.junit.jupiter
    junit-jupiter-params
    5.0.0

最新バージョンはMaven Centralにあります。

@MethodSourceアノテーションを使用すると、テストと同じクラスに存在するproduce test parameters by calling a static functionを使用できます。 これは可能ですが、not obvious in Kotlinです。 use the @JvmStatic annotation inside a companion object:する必要があります

@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)
    )
}

これは、we can only have a single companion object per class以降、パラメーターの生成に使用されるメソッドがすべて一緒でなければならないことも意味します。

パラメータ化されたテストを使用する他のすべての方法は、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言語does not currently allow for repeated annotations。 これにより、wrap them in the @Tags annotation:が必要になるため、タグの使用が少し冗長になります。

@Tags(
    Tag("slow"),
    Tag("logarithms")
)
@Test
fun `Log to base 2 of 8 should be equal to 3`() {
    Assertions.assertEquals(3.0, calculator.log(2, 8))
}

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

6. 概要

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

ただし、多くの場合、これらの構文の変更は、Kotlinを使用するときに読みやすく、操作しやすいです。

これらすべての機能の例は、over on GitHubにあります。