이슈: Junit 5의 테스트 수명주기를 기본값인 테스트 함수당 한 번 대신 클래스 인스턴스당 한 번씩 인스턴스화하고 싶다.
해법: @TestInstance 어노테이션을 사용하거나 junit-platform.properties 파일의 lifecycle.default 속성을 설정하자.
JUnit 4는 각 테스트 메소드마다 테스트 클래스의 새 인스턴스를 생성한다. 이러한 방식은 테스트 클래스 속성이 테스트마다 매번 다시 초기화되므로 테스트 자체로 독립적이다. 단점은 초기화 코드가 각 테스트마다 반복해서 실행된다는 것이다.
자바에서는 이 문제를 해결하기 위해 클래스의 모든 속성을 static으로 표시해 모든 초기화 코드를 딱 한 번만 실행되는 @BeforeClass 어노테이션이 달린 static 메소드 안에 배치할 수 있다. 예제 9-1에서 java.util.List의 JUnit4 테스트를 살펴보자.
이 테스트 클래스에는 strings와 modifiable이라는 두 속성이 있다. modifiable 리스트는 테스트 수명주기를 보여 주려고 구성했다. modifiable 리스트는 선언 시에 빈 리스트로 초기화된 다음 @Before initialize 메소드 안에서 항목을 추가한다. 테스트에 진입할 때마다 빈 modifiable 리스트를 출력한 다음 modifiable 리스트에 원소를 추가한다. 또한 @After 메소드 finish 메소드에도 modifiable리스트가 의도한 원소가 있는지를 보여주기 위해 modifiable 리스트를 출력한다.
코틀린에서 자바와 동일한 동작을 구현하려고 할 때 코틀린에는 static 키워드가 없다는 문제를 바로 직면하게 된다. 동일한 동작을 하도록 코틀린으로 간단히 포팅하려면 예제 9-2에서 보듯이 동반 객체를 사용해야 한다.
class JUnit4ListTests {
companion object { // 1
@JvmStatic // 2
private val strings = listOf("this", "is", "a", "list", "of", "string")
@BeforeClass // 1
@JvmStatic // 2
fun runBefore() {
println("BeforeClass: $strings")
}
@AfterClass // 1
@JvmStatic // 2
fun runAfter() {
println("AfterClass: $strings")
}
}
private val modifiable = ArrayList<Int>()
@Before
fun initialize() {
println("Before: $modifiable")
modifiable.add(3)
modifiable.add(1)
modifiable.add(4)
modifiable.add(1)
modifiable.add(5)
}
@Test
public void test1() {
// ...
}
@Test
public void test2() {
// ...
}
@Test
public void test3() {
// ...
}
@After
fun finish() {
println("After: $modifiable")
}
}
클래스당 한 번씩 실행하도록 동반 객체 사용
static 변경자를 사용해 자바 바이트코드를 생성
동반 객체는 strings 컬렉션이 클래스 전체에서 한 번만 인스턴스화되고 원소가 채워지게 하려고 사용됐다. 같은 이유로 @BeforeClass와 @AfterClass 메소드도 동반 객체 안에서 사용됐다. strings 리스트를 initialize 메소드 안에서 인스턴스화하려면 strings 속성을 val 타입이 아닌 lateinit과 var로 선언해야 한다.
class JUnit4ListTests {
companion object {
@JvmStatic
private lateinit var string
@BeforClass
@JvmStatic
fun runBefore() {
string = listOf("this", "is", "a", "list", "of", "")
}
// ...
}
}
@Before를 꼭 설정해야 하는 경우라도 var 사용은 코틀린 다운 구현에서 멀어진다. 다행히 JUnit5는 더 간단한 방법을 제공한다. JUnit5는 테스트 클래스에서 @TestInstance 어노테이션을 사용해 수명주기를 명시 할수 있다.
import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.assertEquals
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // 1
class JUnit5ListTests {
private val strings = // 2
listOf("this", "is", "a", "list", "of", "strings")
private lateinit var modifiable : MutableList<Int> // 3
@BeforeEach
fun setUp() {
modifiable = mutableListOf(3, 1, 4, 1, 5) // 3
println("before: $modifiable")
}
@AfterEach
fun finish() {
println("After: $modifiable")
}
@Test
fun test1() {
// ...
}
@Test
fun test2() {
// ...
}
@Test
fun test3() {
// ...
}
}
모든 테스트의 테스트 클래스 인스턴스가 오직 하나
인스턴스화와 원소 채움이 오직 한번
테스트할 때마다 실행 전에 다시 초기화
테스트 인스턴스의 수명주기를 PER_CLASS로 설정하면 테스트 함수의 양과 상관 없이 테스트 인스턴스가 딱 하나만 생성된다. 즉 val 변경자의 더불어 strings 속성을 평소와 같은 방법으로 생성하고 원소를 할당할 수 있다.
테스트마다 속성을 초기화해야 하는 경우는 여전히 문제가 발생한다. 이 테스트 클래스에서 modifiable 속성은 lateinit과 var 변경자를 사용하지만 필요하다면 @BeforeEach와 @AfterEach를 사용할 수 있다.