백엔드/Test

[TDD] Junit | Lifecycle Method Annotations - @BeforeEach , @AfterEach , @BeforeAll , @AfterAll

dami97 2024. 2. 12. 23:21
반응형

테스트 코드를 작성하다 보면 각각 중복되는 작업이 많이 생깁니다. 예를 들면 각 테스트 전에 테스트 수행에 필요한 객체를 만들거나 테스트 데이터를 초기화하는 작업 등이 있습니다.
JUnit은 이러한 중복 작업에 대해 편리하게 작업이 가능하도록 라이프 사이클 메서드 어노테이션을 제공합니다.

@BeforeEach
테스트 메서드 전에 실행됩니다.
테스트 전에 필요한 객체를 생성하거나 테스트 데이터 설정에 자주 사용합니다.

@AfterEach
테스트 메서드 후에 실행됩니다.
테스트 후에 테스트 데이터를 다시 초기화 하는 작업에 많이 사용됩니다.

실행 순서 다이어그램을 먼저 보여드리고 코드로 어노테이션 예시를 보여드리겠습니다.

@BeforeEach를 어떤 상황에 적용 가능한지 코드를 통해 보여드리면

// @BeforeEach 적용 전
class DemoUtilsTest {

    @Test
    void testEqualsAndNotEquals() {

        DemoUtils demoUtils = new DemoUtils(); // 중복

        assertEquals(6, demoUtils.add(2, 4), "2 + 4 must be 6");
        assertNotEquals(6, demoUtils.add(1, 9), "1 + 9 must not be 6");
    }

    @Test
    void testNullAndNotNull() {

        DemoUtils demoUtils = new DemoUtils(); // 중복

        assertNull(demoUtils.checkNull(null), "Object should be null");
        assertNotNull(demoUtils.checkNull("dami"), "Object should not be null");
    }

}

코드를 보면 각 테스트 마다 DemoUtils demoUtils = new DemoUtils(); 객체를 생성해서 객체의 메서드를 통해 테스트를 수행하는 것을 볼 수 있습니다.

아래와 같이 테스트 실행 전 필요한 객체생성의 중복 코드를 없앨때 활용됩니다.

// @BeforeEach 적용 후
class DemoUtilsTest {

    private DemoUtils demoUtils;

    @BeforeEach
    void setupBeforeEach(){
        demoUtils = new DemoUtils();
        System.out.println("call @BeforeEach");
    }

    @Test
    void testEqualsAndNotEquals() {

        assertEquals(6, demoUtils.add(2, 4), "2 + 4 must be 6");
        assertNotEquals(6, demoUtils.add(1, 9), "1 + 9 must not be 6");
    }

    @Test
    void testNullAndNotNull() {

        assertNull(demoUtils.checkNull(null), "Object should be null");
        assertNotNull(demoUtils.checkNull("dami"), "Object should not be null");
    }

}

이어서 @AfterEach의 사용 예시를 보여드리겠습니다.
@AfterEach는 각 테스트 수행 후 데이터를 초기화 시킬때 활용됩니다.

// @AfterEach 적용
class DemoUtilsTest {

    private DemoUtils demoUtils;
    private List<String> testNameList;

    @BeforeEach
    void setupBeforeEach(){
        demoUtils = new DemoUtils();
        testNameList = new ArrayList<>();
        System.out.println("call @BeforeEach");
    }

    @AfterEach
    void afterEach(){
        System.out.println("call @AfterEach : " + testNameList.get(0));
        testNameList.remove(0);
    }

    @Test
    void testEqualsAndNotEquals() {
        System.out.println("call testEqualsAndNotEquals()");
        testNameList.add("testEqualsAndNotEquals");

        assertEquals(6, demoUtils.add(2, 4), "2 + 4 must be 6");
        assertNotEquals(6, demoUtils.add(1, 9), "1 + 9 must not be 6");
    }

    @Test
    void testNullAndNotNull() {
        System.out.println("call testNullAndNotNull()");
        testNameList.add("testNullAndNotNull");

        assertNull(demoUtils.checkNull(null), "Object should be null");
        assertNotNull(demoUtils.checkNull("dami"), "Object should not be null");
    }

}
/* console 실행 결과
call @BeforeEach
call testNullAndNotNull()
call @AfterEach : testNullAndNotNull
call @BeforeEach
call testEqualsAndNotEquals()
call @AfterEach : testEqualsAndNotEquals
*/

예시를 위해 좀 억지로 만들어 보았는데 위와 같이 테스트에 사용된 컬렉션 및 데이터를 초기화 하거나 후처리를 하는 경우에 활용할 수 있습니다.

그런데 출력 결과를 보면 @BeforeEach와 @AfterEach는 각각의 테스트 실행전과 실행 후 계속해서 호출되는 것을 볼 수 있습니다.

모든 테스트 실행 전과 실행 후 단 한 번만 실행하고 싶은 코드가 있을 수도 있습니다. 테스트 실행 전 데이터베이스 커넥션을 얻거나 원격 서버에 연결하는 등, 테스트 실행 후 원격 서버와 연결 끊기 등..

Junit 에서 제공하는 @BeforeAll , @AfterAll 을 통해 간단하게 적용 할 수 있습니다.

@BeforeAll
모든 테스트 실행 전 단 한번 실행됩니다.
정적(static) 메서드여야 합니다.

@AfterAll
모든 테스트 실행 후 단 한번 실행됩니다.
정적(static) 메서드여야 합니다.

이번에도 다이어그램을 먼저 보여드리고 코드 예시를 보여드리겠습니다.

코드에 @BeforeAll 과 @AfterAll을 추가해 간단하게 실행 시점을 콘솔에 출력해보겠습니다.

class DemoUtilsTest {

    private DemoUtils demoUtils;
    private List<String> testNameList;

    @BeforeEach
    void setupBeforeEach(){
        System.out.println("call @BeforeEach");
        demoUtils = new DemoUtils();
        testNameList = new ArrayList<>();
    }

    @AfterEach
    void afterEach(){
        System.out.println("call @AfterEach : " + testNameList.get(0));
        testNameList.remove(0);
    }

    @BeforeAll
    static void beforeAll(){
        System.out.println("call @BeforeAll");
    }

    @AfterAll
    static void afterAll(){
        System.out.println("call @AfterAll");
    }

    @Test
    void testEqualsAndNotEquals() {
        System.out.println("call testEqualsAndNotEquals()");
        testNameList.add("testEqualsAndNotEquals");

        assertEquals(6, demoUtils.add(2, 4), "2 + 4 must be 6");
        assertNotEquals(6, demoUtils.add(1, 9), "1 + 9 must not be 6");
    }

    @Test
    void testNullAndNotNull() {
        System.out.println("call testNullAndNotNull()");
        testNameList.add("testNullAndNotNull");

        assertNull(demoUtils.checkNull(null), "Object should be null");
        assertNotNull(demoUtils.checkNull("dami"), "Object should not be null");
    }

}

출력결과 테스트 실행 전 실행 후 단 한번만 호출 된 것을 확인할 수 있습니다.

/* 출력 결과
call @BeforeAll
call @BeforeEach
call testNullAndNotNull()
call @AfterEach : testNullAndNotNull
call @BeforeEach
call testEqualsAndNotEquals()
call @AfterEach : testEqualsAndNotEquals
call @AfterAll
*/

 

참고
@BeforeAll과 @AfterAll 사용 시 메서드를 static으로 선언하지 않으면 아래와 같은 에러가 발생합니다.

@AfterAll 에러 로그
org.junit.platform.commons.JUnitException: @AfterAll method 'void com.dami.unittest.demo.DemoUtilsTest.afterAll()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).

@BeforeAll 에러 로그
org.junit.platform.commons.JUnitException: @BeforeAll method 'void com.dami.unittest.demo.DemoUtilsTest.beforeAll()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).

반응형