Статьи

Котлин и Юнит 5 @ BeforeAll

Вступление

В Kotlin у классов нет статических методов . Java-эквивалентная семантика может быть предоставлена ​​вызывающим абонентам, используя концепцию сопутствующего объекта . В этом посте будет подробно рассказано о том, что требуется для поддержки аннотаций JUnit 5 @BeforeAll и @AfterAll, которые зависят от прецедента статических методов в тестовых классах.

До и после и все в Java

Junit 5 @BeforeAll аннотированные методы выполняются до всех тестов, а @AfterAll ожидается после всех тестов. Предполагается, что эти аннотации будут применяться к статическим методам:

01
02
03
04
05
06
07
08
09
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», это подтверждается журналами, посмотрите, как отличаются идентификаторы экземпляра (из 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 можно размещать в нестатических методах, поскольку платформа JUnit 5 может гарантировать, что эти методы будут ровно один раз перед ВСЕМИ тестами. Суть в том, что любое состояние уровня экземпляра не будет сбрасываться перед каждым тестом.

До и после и все в Котлине

Так как же это перевести на Kotlin —

Для случая по умолчанию нового экземпляра теста для каждого теста эквивалентный код теста Kotlin выглядит следующим образом:

01
02
03
04
05
06
07
08
09
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")
        }
    }
}

Сопутствующий объект Kotlin с методами, аннотированными @JvmStatic, выполняет эту работу.

Упрощен тот случай, когда жизненный цикл изменяется:

01
02
03
04
05
06
07
08
09
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 воздействовал на экземпляр теста (вставляет зависимости, разрешает свойства и т. Д.) Только после вызова аннотированного метода @BeforeAll, чтобы конкретнее рассмотреть следующий пример:

01
02
03
04
05
06
07
08
09
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 способствовал моему пониманию нюансов JUnit 5 с Kotlin.

Смотреть оригинальную статью здесь: Kotlin и JUnit 5 @BeforeAll

Мнения, высказанные участниками Java Code Geeks, являются их собственными.