4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

KotlinAdvent Calendar 2022

Day 1

Kotlin TestのParameterizedなTestを可読性/メンテナビリティ高く実装する方法

Last updated at Posted at 2022-11-30

概要

ロジックや仕様をテストで守っていると開発がしやすいですよね。
リファクタが気軽にできるようになったり、デグレをテストが教えてくれたり、画面試験の前にエラーに気づけたり。

そんなテストを実践で使っているなか、可読性を大きく改善した取り組みがあったので紹介します。

結論を言うと Parameterized Test を使うのですが、最初から入っているものは可読性/メンテナビリティにストレスがあったので使いません!
とてもシンプルな関数を足すことで Parameterized Test 的なことを可読性/メンテナビリティ高く実現できましたので紹介します。

JUnit 4 でも
JUnit 5 でも使えます。

Parameterized Test

テストの可読性をあげるなら Parameterized Test の思想は欠かせません。
Parameterized Test というのは複数のケースを1つにまとめて記載する方法で、差分だけを引数で渡す形でテストできるものです。
重複コードが減りますし、確認するコードが引数によってどういう変化を起こすのか直感的にわかるようにできます。

JUnit5 にはこれを行うための @ParameterizedTest という仕組みがありますが(JUnit4 でもモジュールを入れることで同等のことができるのですが)、ここではさらに見やすくするために行ったことをご紹介します。

@ParameterizedTest の何が不満だったのか

たとえばこういうイメージ:

JUnit5 の ParameterizedTest
@ParameterizedTest
@ArgumentsSource(TestCase::class)
fun hoge(value: Int, expected: Int) {
    val actual = hoge.aaa(value)
    Assert.assertEquals(expected, actual)
}

private class TestCase : ArgumentsProvider {
    override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> = Stream.of(
            arguments(1, 101),
            arguments(2, 102),
    )
}

テストの記述とテストケースの記述が離れてしまうんですよね。
これがテストケースがたくさん並んでくると結構ストレスでした。

改善版

これを解決するために下記のクラスを作成しました。

ParameterizedTest を改善するために作った関数
abstract class TestBase {
    open setUp() {
    }

    fun <T> cases(vararg cases: T, block: (T) -> Unit) {
        for (c in cases) {
            setUp()
            try {
                block(c)
            } catch (e: Throwable) {
                System.err.println("CASE = $c")
                throw e
            }
            clearAllMocks()
        }
    }
}

このシンプルなクラスを作るだけで Parameterized Test が可読性高く書けるようになります。

// TestBase を継承して利用します。

@Test
fun hoge() = cases(
    1 to 101,
    2 to 102,
) { (value, expected) ->
    val actual = hoge.aaa(value)
    Assert.assertEquals(expected, actual)
}

エラーの場合は、どのケースでエラーが出たのかは冒頭で出力されるので特定できます。

image.png

簡単ですね!

たくさんのデータを引数で渡したいなら

data class 作ればOKです。

@Test
fun hoge() = cases(
    HogeData(1, 2, 3, 101),
    HogeData(4, 5, 6, 102),
) { c ->
    val actual = hoge.aaa(c.x, c.y, c.z)
    Assert.assertEquals(c.expected, actual)
}

data class HogeData(
    val x: Int,
    val y: Int,
    val z: Int,
    val expected: Int,
)

簡単ですよね!

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?