MPのご利用は計画的に

だいたい自分用のメモ

KotlinとJUnit5とTestInstanceアノテーション

概要

JavaでJUnit5を使っているときに、KotlinだとBeforeAllAfterAllはどうなるのだろうと試したメモです。

環境

  • Kotlin 1.2.60
  • JUnit 5.2.0(jupiter)

試したこと

インスタンスメソッドでいい?

インスタンスメソッドにBeforeAllAfterAllアノテーションをつけると怒られます。 (IDEA使ってるとメソッド名がグレーアウトになるので、事前に実行されなさそうなこともわかります)

package com.example.panage.junit5

import org.junit.jupiter.api.*


class TestInstancePerMethodSample {
    @BeforeAll
    fun execBeforeAllTest() {
        println("before all")
    }

    @BeforeEach
    fun execBeforeEachTest() {
        println("before each")
    }

    @Test
    fun hoge() {
        println("feature")
    }

    @AfterEach
    fun execAfterEachTest() {
        println("after each")
    }

    @AfterAll
    fun execAfterAllTest() {
        println("after all")
    }
}
org.junit.platform.commons.JUnitException: @BeforeAll method 'public final void com.example.panage.junit5.TestInstanceSample.execBeforeAllTest()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).

    at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.assertStatic(LifecycleMethodUtils.java:59)
    at org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.lambda$findMethodsAndAssertStatic$0(LifecycleMethodUtils.java:83)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1080)

staticメソッドにするか、TestInstanceアノテーション(後述)を使うように言われます。

companion objectだけでいい?

companion objectの中に入れてみます。

    companion object {
        @BeforeAll
        fun execBeforeAllTest() {
            println("before all")
        }

        @AfterAll
        fun execAfterAllTest() {
            println("after all")
        }
    }
before each
feature
after each

Process finished with exit code 0

テストは実行できますが、BeforeAllAfterAllの2つは実行されません。

JvmStaticアノテーションをつけたら?

@JvmStaticを追加してみます。

    companion object {
        @BeforeAll
        @JvmStatic
        fun execBeforeAllTest() {
            println("before all")
        }

        @AfterAll
        @JvmStatic
        fun execAfterAllTest() {
            println("after all")
        }
    }
before all
before each
feature
after each
after all
Process finished with exit code 0

ちゃんとBeforeAllAfterAllのメソッドも実行されました。

TestInstanceアノテーションなら?

先程のエラーメッセージでTestInstanceアノテーションを使うように言われていたのでつけてみます。 クラスに@TestInstance(TestInstance.Lifecycle.PER_CLASS)を足して、BeforeAllAfterAllcompanion objectの外に出します。

package com.example.panage.junit5

import org.junit.jupiter.api.*

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestInstancePerClassSample {

    @BeforeAll
    fun execBeforeAllTest() {
        println("before all")
    }

    @AfterAll
    fun execAfterAllTest() {
        println("after all")
    }

    @BeforeEach
    fun execBeforeEachTest() {
        println("before each")
    }

    @Test
    fun testFeature() {
        println("feature")
    }

    @AfterEach
    fun execAfterEachTest() {
        println("after each")
    }
}
before all
before each
feature
after each
after all
Process finished with exit code 0

ちゃんと実行できました!

TestInstanceアノテーション

テストクラスのライフサイクルを変更するためのアノテーションです(そのまんま) PER_CLASSPER_METHODが用意されていて、何も設定していない状態だとPER_METHODになります。

PER_METHODは従来どおりの動作で、テストメソッド実行ごとにテストクラスのインスタンスが生成されます。
PER_CLASSでは各テストメソッドに、同じインスタンスが使われます。(@Nestedを併せて使うと少し話は変わってくるようです)

設定の変更はアノテーションの他に、MavenやSystemプロパティなどからデフォルトでどちらにするかを変えられます。 junit-platform.propertiesというファイルに書いてクラスパスルートに置くのが個人的には良いかな?と思っています。

TestInstanceとKotlinの関係はAPIドキュメントにも

Simplified declaration of @BeforeAll and @AfterAll methods in test classes implemented with the Kotlin programming language.

とあります。

デフォルトの設定を変えるのか、クラスごとに設定するのかはチームごとの方針に依るとは思いますが、 KotlinでBeforeAllAfterAllを使うときは、PER_CLASSにしておくと楽になりそうです。

参考

TestInstance (JUnit 5.1.0 API)