Kotlin和JUnit 5 @BeforeAll

Kotlin and JUnit 5 @BeforeAll

在Kotlin中,类没有静态方法。但是,可以使用伴随对象的概念向调用者提供Java等效语义。这篇文章将详细介绍支持JUnit 5 @BeforeAll和@AfterAll批注所需的工作,这取决于测试类中是否存在静态方法。

Java中的BeforeAll和AfterAll

Junit 5 @BeforeAll注释的方法在所有测试之前执行,@ AfterAll在所有测试之后执行。这些注释应应用于静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Junit5BeforeAllTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(Junit5BeforeAllTest.class);

    @BeforeAll
    static void beforeAll() {
        LOGGER.info("beforeAll called");    
    }

    @Test
    public void aTest1() {
        LOGGER.info("aTest1 called");
        LOGGER.info(this.toString());        
    }

    @Test
    public void aTest2() {
        LOGGER.info("aTest2 called");
        LOGGER.info(this.toString());
    }

    @AfterAll
    static void afterAll() {
        LOGGER.info("afterAll called");        
    }
}

大致的流程是:JUnit平台调用带注释的" @BeforeAll"方法,然后为每个测试 创建测试类的实例并调用测试。毕竟,将执行测试,然后将调用带有" @AfterAll"注释的静态方法。

这由日志证实。查看实例id(从Object的toString())与众不同:

1
2
3
4
5
6
2018-03-28 17:22:03.618  INFO   --- [           main] c.p.cookbook.Junit5BeforeAllTest         : beforeAll called
2018-03-28 17:22:03.652  INFO   --- [           main] c.p.cookbook.Junit5BeforeAllTest         : aTest1 called
2018-03-28 17:22:03.653  INFO   --- [           main] c.p.cookbook.Junit5BeforeAllTest         : com.pivotalservices.cookbook.Junit5BeforeAllTest@7bc1a03d
2018-03-28 17:22:03.663  INFO   --- [           main] c.p.cookbook.Junit5BeforeAllTest         : aTest2 called
2018-03-28 17:22:03.664  INFO   --- [           main] c.p.cookbook.Junit5BeforeAllTest         : com.pivotalservices.cookbook.Junit5BeforeAllTest@6591f517
2018-03-28 17:22:03.669  INFO   --- [           main] c.p.cookbook.Junit5BeforeAllTest         : afterAll called

但是,如果通过以下方式对测试类进行注释,则可以通过注释来更改JUnit 5测试的默认生命周期:

1
2
3
4
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class Junit5BeforeAllTest {
    ....
}

现在的优点是可以将@BeforeAll和@AfterAll批注放置在非静态方法上。但是要注意的是,在每次测试之前都不会重置任何实例级状态。

在Kotlin之前和之后

那么这如何转换成Kotlin?

对于每个测试一个新测试实例的默认情况,等效的Kotlin测试代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.slf4j.LoggerFactory

class Junit5BeforeAllKotlinTest {

    @Test
    fun aTest1() {
        LOGGER.info("aTest1 called")
        LOGGER.info(this.toString())
    }

    @Test
    fun aTest2() {
        LOGGER.info("aTest2 called")
        LOGGER.info(this.toString())
    }

    companion object {
        private val LOGGER = LoggerFactory.getLogger(Junit5BeforeAllTest::class.java)


        @BeforeAll
        @JvmStatic
        internal fun beforeAll() {
            LOGGER.info("beforeAll called")
        }

        @AfterAll
        @JvmStatic
        internal fun afterAll() {
            LOGGER.info("afterAll called")
        }
    }
}

带有@JvmStatic注释的方法的Kotlincompanion对象可以完成此工作。

修改生命周期的情况比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.slf4j.LoggerFactory

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

    private val LOGGER = LoggerFactory.getLogger(Junit5BeforeAllTest::class.java)

    @BeforeAll
    internal fun beforeAll() {
        LOGGER.info("beforeAll called")
    }

    @Test
    fun aTest1() {
        LOGGER.info("aTest1 called")
        LOGGER.info(this.toString())
    }

    @Test
    fun aTest2() {
        LOGGER.info("aTest2 called")
        LOGGER.info(this.toString())
    }


    @AfterAll
    internal fun afterAll() {
        LOGGER.info("afterAll called")
    }
}

我个人偏爱伴侣对象方法,因为我喜欢在执行测试方法之前确定测试实例的确定性状态的想法。该方法的另一个优势是基于Spring Boot的测试,您希望Spring仅在调用a @ BeforeAll-annotated方法之后对测试实例执行操作(注入依赖项,解析属性等)。为了更具体,请考虑以下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Configuration
import org.springframework.test.context.junit.jupiter.SpringExtension


@ExtendWith(SpringExtension::class)
@SpringBootTest
class BeforeAllSampleTest {

    @Value("\${some.key}")
    private lateinit var someKey: String


    companion object {
        @BeforeAll
        @JvmStatic
        fun beforeClass() {
            System.setProperty("some.key","some-value")
        }

        @AfterAll
        @JvmStatic
        fun afterClass() {
            System.clearProperty("some.key")
        }
    }

    @Test
    fun testValidateProperties() {
        assertThat(someKey).isEqualTo("some-value")
    }

    @Configuration
    class SpringConfig
}

如果将生命周期更改为" @TestInstance(TestInstance.Lifecycle.PER_CLASS)",则这种测试根本无法进行。

参考

这个StackOverflow答案有助于我理解Kotlin对JUnit 5的细微差别。