[JUnit5] 테스트 작성(5) - 라이프사이클과 테스트 순서(Order)
JAVA/JUnit

[JUnit5] 테스트 작성(5) - 라이프사이클과 테스트 순서(Order)

라이프 사이클

JUnit의 중요한 특성 중 하나는 테스트들을 실행할 때 각 테스트 클래스의 새 인스턴스를 만들어 각각 독립적으로 실행되는 것입니다. 이를 통해 테스트 인스턴스의 예상치 못한 부작용을 피할 수 있습니다.

public class MethodLifecycleTest {
    int count = 1;

    @BeforeAll
    static void init() {
        System.out.println("===테스트 시작===");
    }

    @AfterAll
    static void destroy() {
        System.out.println("===테스트 종료===");
    }
    
    @DisplayName("테스트1")
    @Test
    void plus1() throws Exception{
        System.out.println(++count);		// 결과 = 2
        System.out.println(this);		// 인스턴스a
    }

    @DisplayName("테스트2")
    @Test
    void plus2() throws Exception{
        System.out.println(++count);		// 결과 = 2
        System.out.println(this);		// 인스턴스b
    }

    @DisplayName("테스트3")
    @Test
    void plus3() throws Exception{
        System.out.println(++count);		// 결과 = 2
        System.out.println(this);		// 인스턴스c
    }
}

해당 코드를 보면, 클래스의 생명주기(Life Cycle)가 각 메서드까지 인 것을 알 수 있습니다.

 

만약 클래스가 stateless 해 불필요한 인스턴스 생성을 피하고 싶은 경우(굳이 독립적일 필요가 없을 때)에는 이 생명주기를 '@TestInstance'를 통해 클래스 단위로 변경할 수 있습니다. 

@TestInstance(TestInstance.Lifecycle.PER_CLASS)	// 라이프사이클을 클래스레벨로 올림
public class MethodLifecycleTest {
    int count = 1;

    // 생명주기가 클래스레벨일 경우에는 @BeforeAll @AfterAll에 static 선언을 하지 않아도 된다.
    @BeforeAll	
    void init() {
        System.out.println("===테스트 시작===");
    }

    @AfterAll
    void destroy() {
        System.out.println("===테스트 종료===");
    }
    
    @DisplayName("테스트1")
    @Test
    void plus1() throws Exception{
        System.out.println(++count);		// 결과 = 2
        System.out.println(this);		// 인스턴스a
    }

    @DisplayName("테스트2")
    @Test
    void plus2() throws Exception{
        System.out.println(++count);		// 결과 = 3
        System.out.println(this);		// 인스턴스a
    }

    @DisplayName("테스트3")
    @Test
    void plus3() throws Exception{
        System.out.println(++count);		// 결과 = 4
        System.out.println(this);		// 인스턴스a
    }
}

여기서 '반드시 재설정해야 하는 인스턴스가 있다.'라고 하면, 이는 @BeforeEach@AfterEach에서 재설정해주시면 됩니다!

추가적으로 생명주기가 클래스 레벨인 경우에는 이미 정적이기 때문에 굳이 @BeforeAll이나, @AfterAll에서 static 선언을 하지 않아도 됩니다.

 

추가적으로 @Nested를 클래스에서 추가적으로 @BeforeAll, @AfterAll을 설정할 수 있습니다. (@BeforeEach, @AfterEach는 계속 공유해서 사용.)

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MethodLifecycleTest {
    int count = 1;

    @BeforeAll
    void init() {
        System.out.println("===테스트 시작===");
    }

    @AfterAll
    void destroy() {
        System.out.println("===테스트 종료===");
    }

    @BeforeEach
    void initMethod() {
        System.out.println("---단위 테스트 시작---");
    }

    ...

    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    @Nested
    class innerTest {
    
    	// 각 Nested 클래스에서 재사용이 가능해진다.
        @BeforeAll
        void initA() {
            System.out.println("===이너 클래스 테스트 시작===");
        }

        @AfterAll
        void destroyA() {
            System.out.println("===이너 클래스 테스트 시작===");
        }

        @Test
        void anotherTest() {
            System.out.println(++count);
            System.out.println(this);
        }
    }
}

당연하지만 Java의 특성상 일반적인 static 선언으로는 불가능합니다.

 

 

테스트 메서드 실행 순서

사실 각 테스트 메서드(단위 테스트)들은 실행에 정해진 순서가 없습니다. 무작위로 실행된다는 것입니다!

왜냐하면, 각 단위 테스트들이 독립적으로 수행되어야 하기 때문에, 굳이 순서에 의존할 필요가 없기 때문입니다.

 

하지만, 함수 구현 테스트나, 통합 테스트 같이 실행 순서를 특정해야 하는 경우도 있습니다. 이런 상황에서 '@TestMethodOrder'을 사용하시면 됩니다. 이 어노테이션의 파라미터는 MethodOrderer의 구현체를 받는데, 이 구현체에 따라 우선순위를 결정할 수 있습니다. JUnit에서는 기본적으로 여러 구현체를 지원합니다.

MethodOrderer 구현체 설명
Alphanumeric 이름과 파라미터 목록을 기반으로 테스트 순서를 영숫자 순으로 정렬하는 방법입니다.
(6.0에서 MethodName로 기능이 넘어가, 제거될 예정입니다.)
DisplayName @DisplayName에 따라 테스트 메서드를 영숫자 순으로 정렬하는 방법입니다.
MethodName 이름과 파라미터 목록을 기반으로 테스트 순서를 영숫자 순으로 정렬하는 방법입니다.
(Alphanumeric을 대체할 구현체입니다.)
OrderAnnotation @Order(int) 어노테이션을 통해 테스트 메서드를 정렬합니다.
Random 정해진 로직이 아니라, 무작위로 실행합니다.

추가적으로 이렇게 순서가 필요한 경우에는, 인스턴스를 공유해야 할 경우가 많기 때문에 '@TestInstance(TestInstance.Lifecycle.PER_CLASS)'의 사용이 선행되어 집니다.

 

원하는 순서가 없다면 따로 커스텀 순서를 구현할 수 있습니다.

OrderAnnotation만 짚고 넘어갑시다!

 

어노테이션을 이용한 순서 (OrderAnnotation)

각 테스트 메서드(단위 테스트)에 '@Order(int)'를 이용해 순서를 결정하는 방법입니다.

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class MethodOrderTest {
    @BeforeAll
    static void init() {
        System.out.println("===테스트 시작===");
    }

    @AfterAll
    static void destroy() {
        System.out.println("===테스트 종료===");
    }

    @Test
    @Order(2)		// 두 번째 실행
    void test1() throws Exception{
        System.out.println("test1 실행");
        System.out.println(this);
    }

    @Test
    @Order(1)		// 첫 번째 실행
    void test2() throws Exception{
        System.out.println("test2 실행");
        System.out.println(this);
    }

    @Test
    @Order(3)		// 세 번째 실행
    void test3() throws Exception{
        System.out.println("test3 실행");
        System.out.println(this);
    }
}

 

실행 순서 전체 적용

만약 각 클래스가 아닌, 프로젝트의 테스트 전체에 해당 실행 순서를 적용시키고 싶은 경우

'junit-platform.properties'에서 설정이 가능합니다.

예시

# OrderAnnotation 순서방식으로 테스트 실행
junit.jupiter.testmethod.order.default = \
    org.junit.jupiter.api.MethodOrderer$OrderAnnotation