1. 序章

新しくリリースされたJUnit5 は、Java用のよく知られたテストフレームワークの次のバージョンです。 このバージョンには、Java 8 で導入された機能を特に対象とする多くの機能が含まれています。これは、主にラムダ式の使用を中心に構築されています。

この簡単な記事では、同じツールがKotlin言語でどのように機能するかを示します。

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の場合とまったく同じように、テストクラスのフィールドを操作することもできます。

必要なインポートは異なることに注意してください。は、Assertクラスの代わりにAssertionsクラスを使用してアサーションを実行します。 これはJUnit5の標準的な変更であり、Kotlinに固有のものではありません。

先に進む前に、テスト名を変更して、Kotlinで b acktick識別子を使用しましょう。

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

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

3. 高度なアサーション

JUnit 5は、ラムダを操作するための高度なアサーションを追加します。 これらはKotlinでもJavaでも同じように機能しますが、言語の動作方法のために少し異なる方法で表現する必要があります。

3.1. 例外の主張

JUnit 5は、呼び出しが例外をスローすると予想される場合のアサーションを追加します。 メソッド内の呼び出しだけでなく、特定の呼び出しが予期された例外をスローすることをテストできます。例外自体をアサートすることもできます。

Javaでは、ラムダをAssertions.assertThrowsの呼び出しに渡します。 Kotlinでも同じことを行いますが、アサーション呼び出しの最後にブロックを追加することで、コードを読みやすくすることができます。

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

このコードは、同等のJavaとまったく同じように機能しますが、 assertThrows 関数を呼び出す括弧内にラムダを渡す必要がないため、読みやすくなります

3.2. 複数のアサーション

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

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

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

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

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を使用すると、例外をテストするために上記で見たのと同じ方法で、ラムダを渡すことができます。 メソッド参照を渡すこともできます。 これは、 List.isEmpty を使用してここで行うように、既存のオブジェクトの戻り値をテストするときに特に役立ちます。

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

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

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

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

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

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

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

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

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

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

@MethodSource アノテーションを使用すると、テストと同じクラスにある静的関数を呼び出すことにより、テストパラメーターを生成できます。 これは可能ですが、Kotlinではは明らかではありません。 コンパニオンオブジェクト内で@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)
    )
}

これは、クラスごとに1つのコンパニオンオブジェクトしか持てないため、パラメータの生成に使用されるメソッドはすべて一緒でなければならないことも意味します

パラメータ化されたテストを使用する他のすべての方法は、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 `Log to base 2 of 8 should be equal to 3`() {
    Assertions.assertEquals(3.0, calculator.log(2, 8))
}

これはJava7でも必要であり、すでにJUnit5で完全にサポートされています。

6. 概要

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

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

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