9장 테스트

9.1 테스트 클래스 수명주기 설정하기

이슈: Junit 5의 테스트 수명주기를 기본값인 테스트 함수당 한 번 대신 클래스 인스턴스당 한 번씩 인스턴스화하고 싶다.

해법: @TestInstance 어노테이션을 사용하거나 junit-platform.properties 파일의 lifecycle.default 속성을 설정하자.

JUnit 4는 각 테스트 메소드마다 테스트 클래스의 새 인스턴스를 생성한다. 이러한 방식은 테스트 클래스 속성이 테스트마다 매번 다시 초기화되므로 테스트 자체로 독립적이다. 단점은 초기화 코드가 각 테스트마다 반복해서 실행된다는 것이다.

자바에서는 이 문제를 해결하기 위해 클래스의 모든 속성을 static으로 표시해 모든 초기화 코드를 딱 한 번만 실행되는 @BeforeClass 어노테이션이 달린 static 메소드 안에 배치할 수 있다. 예제 9-1에서 java.util.List의 JUnit4 테스트를 살펴보자.

public class JUnit4ListTest {
    private static List<String> string = // 1
        Arrays.asList("this", "is", "a", "list", "of", "strings");
        
    private List<Integer> modifiable = new ArrayList<>(); // 2
    
    @BeforeClass // 1
    public static void runBefore() {
        System.out.println("BeforeClass: " + strings");
    }
    
    @Before // 2
    public void initialize() {
        System.out.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
    public void finish() {
        System.out.print("After: " + modifiable);
    }
    
    @AfterClass
    public static void runAfter() {
        System.out.println("AfterClass: " + strings);
    }
}
  1. 전체 클래스에서 한 번만 실행

  2. 테스트 메소드당 한 번씩 실행

이 테스트 클래스에는 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")
    }
    
}
  1. 클래스당 한 번씩 실행하도록 동반 객체 사용

  2. 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() {
        // ...
    }
    
}
  1. 모든 테스트의 테스트 클래스 인스턴스가 오직 하나

  2. 인스턴스화와 원소 채움이 오직 한번

  3. 테스트할 때마다 실행 전에 다시 초기화

테스트 인스턴스의 수명주기를 PER_CLASS로 설정하면 테스트 함수의 양과 상관 없이 테스트 인스턴스가 딱 하나만 생성된다. 즉 val 변경자의 더불어 strings 속성을 평소와 같은 방법으로 생성하고 원소를 할당할 수 있다.

테스트마다 속성을 초기화해야 하는 경우는 여전히 문제가 발생한다. 이 테스트 클래스에서 modifiable 속성은 lateinit과 var 변경자를 사용하지만 필요하다면 @BeforeEach와 @AfterEach를 사용할 수 있다.

Last updated

Was this helpful?