👋

KotlinのMethodSourceのParametrizedTestを見やすく書く

に公開

Kotlinでテストを書く際に、MethodSourceを使ったParametrizedTestをより可読性高く書く方法を紹介します。本記事では、課題とその解決方法を具体的なサンプルコードとともに解説します。

🚨 課題

MethodSourceを利用する際、JavaではStaticメソッドとして記述する必要があります。しかし、KotlinではStaticメソッドの代わりにCompanion Objectを使って定義することになります。

Companion Objectの課題

  • inner classでは定義できない
    Companion Objectはテストクラスのトップレベルでしか定義できません。そのため、テストケースとMethodSourceの距離が離れ、可読性が低下するという問題があります。

✅ 解決方法

@TestInstance(TestInstance.Lifecycle.PER_CLASS)を活用

@TestInstance(TestInstance.Lifecycle.PER_CLASS)アノテーションを利用することで、MethodSourceをテストケースに近いinner class内のインスタンスメソッドとして定義できます。これにより、テストコードの可読性が向上します。


🧪 サンプルコード

以下は、@TestInstanceを活用してMethodSourceをinner classのインスタンスメソッドとして定義したサンプルコードです。

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream

class PendingExtensionFieldsTest {

    @Nested
    @TestInstance(TestInstance.Lifecycle.PER_CLASS) // Allow MethodSource to be instance method close to the testcase.
    inner class GetByIdTest {

        @ParameterizedTest
        @MethodSource("extensionFieldsProvider")
        fun `returns the extension field corresponding to the given id`(
            fieldsUnderTest: PendingExtensionFields,
            fieldId: Int, // value class is recognized as Raw Tyep via MethodSource
            expected: ExtensionField?
        ) {
            // Given / When / Then
            assertThat(fieldsUnderTest.getByExtensionFieldId(ExtensionFieldId(fieldId))).isEqualTo(expected)
        }

        private fun extensionFieldsProvider(): Stream<Arguments> {
            val field1 = ExtensionField1("value")
            val field2 = ExtensionField2("value")
            val field3 = ExtensionField3("value")
            val field4 = ExtensionField4("value")
            val field5 = ExtensionField5("value")
            val field6 = ExtensionField6("value")
            val field7 = ExtensionField7("value")
            val field8 = ExtensionField8("value")
            val field9 = ExtensionField9("value")
            val field10 = ExtensionField10("value")
            val field11 = ExtensionField11("value")
            val field12 = ExtensionField12("value")
            val field13 = ExtensionField13("value")
            val field14 = ExtensionField14("value")
            val field15 = ExtensionField15("value")
            val field16 = ExtensionField16("value")
            val field17 = ExtensionField17("value")
            val field18 = ExtensionField18("value")
            val field19 = ExtensionField19("value")

            return Stream.of(
                Arguments.of(PendingExtensionFields(field1 = field1), 1, field1),
                Arguments.of(PendingExtensionFields(field2 = field2), 2, field2),
                Arguments.of(PendingExtensionFields(field3 = field3), 3, field3),
                Arguments.of(PendingExtensionFields(field4 = field4), 4, field4),
                Arguments.of(PendingExtensionFields(field5 = field5), 5, field5),
                Arguments.of(PendingExtensionFields(field6 = field6), 6, field6),
                Arguments.of(PendingExtensionFields(field7 = field7), 7, field7),
                Arguments.of(PendingExtensionFields(field8 = field8), 8, field8),
                Arguments.of(PendingExtensionFields(field9 = field9), 9, field9),
                Arguments.of(PendingExtensionFields(field10 = field10), 10, field10),
                Arguments.of(PendingExtensionFields(field11 = field11), 11, field11),
                Arguments.of(PendingExtensionFields(field12 = field12), 12, field12),
                Arguments.of(PendingExtensionFields(field13 = field13), 13, field13),
                Arguments.of(PendingExtensionFields(field14 = field14), 14, field14),
                Arguments.of(PendingExtensionFields(field15 = field15), 15, field15),
                Arguments.of(PendingExtensionFields(field16 = field16), 16, field16),
                Arguments.of(PendingExtensionFields(field17 = field17), 17, field17),
                Arguments.of(PendingExtensionFields(field18 = field18), 18, field18),
                Arguments.of(PendingExtensionFields(field19 = field19), 19, field19),
                Arguments.of(PendingExtensionFields(), 20, null),
            )
        }
    }
}


🔗 参考

  • ArgumentsSourceを使った方法

    テストクラスのトップレベルでしか使用できませんが、別の方法として考慮できます。

    詳細はこちら

  • @TestInstance(TestInstance.Lifecycle.PER_CLASS)について

    このアノテーションがどのようなものかを理解することは重要です。

    詳細はこちら

Discussion