Java/Junit 5

Junit 5 – @TestInstance

kjwc 2023. 3. 8. 22:14
728x90

Junit5 @TestInstance Annotation 과 @TestInstance Annotation을 사용하여 테스트 인스턴스 수명 주기 동작을 변경하는 방법에 대하여 알아보도록 하겠습니다.

 

1. Junit 5 테스트 인스턴스 수명 주기

JUnit은 각 테스트 메서드를 실행하기 전에 각 테스트 클래스의 새 인스턴스를 만듭니다.
Junit5 테스트 인스턴스 수명 주기의 기본 동작은 '메소드별'입니다.
이것은 @TestInstance 주석을 사용하여 변경할 수 있습니다.

Junit 5는 두 가지 인스턴스 수명 주기 모드를 지원합니다.
1. Lifecycle per-method(메서드별 수명 주기)
2. Lifecycle per-class(클래스별 수명 주기)

테스트 클래스에 @TestInstance Annotation을 추가하여 테스트에 대한 인스턴스 수명 주기 모드를 설정할 수 있습니다.

 

2. Lifecycle per-method mode (메서드별 수명 주기 모드)

Junit의 모든 버전에 대한 기본 동작입니다. 이 모드를 사용하면 각 테스트 방법, 테스트 팩토리 방법 또는 테스트 템플릿 방법에 대해 새 테스트 인스턴스가 생성됩니다.

 

@TestInstance(Lifecycle.PER_METHOD) 사용예

 

1
2
3
4
5
6
7
8
9
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
42
43
package com.tistory.itbaewom.instance;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import com.tistory.itbaewom.util.Calculator;
 
 
@TestInstance(Lifecycle.PER_METHOD)
public class CalculatorTestPerMethod {
 
    private int result = 5;
 
    @Test
    void test_addition() {
        result = Calculator.addition(result, 2);
        System.out.println("Calculator.addition(result, 2) ===> " + result);
        assertEquals(7, result);
    }
 
    @Test
    void test_subtraction() {
        result = Calculator.subtraction(result, 2);
        System.out.println("Calculator.subtraction(result, 2) ===> " + result);
        assertEquals(3, result);
    }
 
    @Test
    void test_multiplication() {
        result = Calculator.multiplication(result, 2);
        System.out.println("Calculator.multiplication(result, 2) ===> " + result);
        assertEquals(10, result);
    }
 
    @Test
    void test_division() {
        result = Calculator.division(result, 2);
        System.out.println("Calculator.division(result, 2) ===> " + result);
        assertEquals(2, result);
    }
}
cs

 

테스트 수행 결과 입니다.

 

 

 

메서드 별로 객체가 생성되어 항상 result의 값이 5입니다.

5 - 2 =   3

5 * 2 = 10

5 / 2 =   2

5 + 2 =  7

 

 

3. Lifecycle per-class mode(클래스별 수명 주기 모드)

이 모드를 사용하면 테스트 클래스당 한 번씩 새 테스트 인스턴스가 생성됩니다.

 

3-1. 이 모드에서는 주어진 테스트 클래스의 테스트 메서드와 비정적 @BeforeAll 및 @AfterAll 간의 테스트 인스턴스 상태를 공유합니다.

 

@TestInstance(Lifecycle.PER_CLASS) 사용 예

 

1
2
3
4
5
6
7
8
9
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
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.tistory.itbaewom.instance;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import com.tistory.itbaewom.util.Calculator;
 
 
@TestInstance(Lifecycle.PER_CLASS)
public class CalculatorTestPerClass {
 
    private int result = 5;
 
    @BeforeAll // 새 테스트 인스턴스 생성시 1회
    void beforeAll() {
        System.out.println(">>>> @BeforeAll : 테스트를 시작합니다.\n");
    }
 
    @BeforeEach // 메서드 테스트 실행전 마다
    void beforeEach(TestInfo testInfo) {
        System.out.println("@BeforeEach : " + testInfo.getTestMethod().get().getName() + "메서드 테스트 수행시작!!!");
    }
 
    @Test
    void test_addition() {
        result = Calculator.addition(result, 2);
        System.out.println("Calculator.addition(result, 2) ===> " + result);
        assertEquals(5, result);
    }
 
    @Test
    void test_subtraction() {
        result = Calculator.subtraction(result, 2);
        System.out.println("Calculator.subtraction(result, 2) ===> " + result);
        assertEquals(3, result);
    }
 
    @AfterEach // 메서드 테스트 실행후 마다
    void afetrEach(TestInfo testInfo) {
        System.out.println("@AfterEach : " + testInfo.getTestMethod().get().getName() + "메서드 테스트 수행종료!!!\n");
    }
 
    @AfterAll // 새 테스트 인스턴스 소멸시 1회
    void afetrAll() {
        System.out.println(">>>> @AfterAll : 테스트를 종료합니다.");
    }
}
cs

 

테스트 수행 결과 입니다.

 

클래스당 한 번씩 새 테스트 인스턴스가 생성됩니다. 그래서 result 변수의 값을 모든 데스트 메서드가 공유합니다.

5 - 2 = 3

3 + 2 = 5

 

@BeforeAll : 객체 생성시 1회만 실행됩니다.

@AfterAll : 객체 소멸시 1회만 실행됩니다.

 

@BeforeEach : 테스트 메서드(@Test 가 붙은 메서드) 실행 전에 매번  실행됩니다.

@AfterEach : 테스트 메서드(@Test 가 붙은 메서드) 실행 후에 매번  실행됩니다.

 

 

 

 

3-2. 테스트 인스턴스 수명 주기의 기본 모드는 PER_METHOD입니다.

테스트 인스턴스 수명 주기 모드를  Lifecycle.PER_CLASS로 설정하지 않은 경우 @BeforeAll 및 @AfterAll 콜백 메서드는 정적(static) 메서드 이어야 합니다. 그렇지 않으면 테스트 수행시 오류가 발생합니다.

 

1
2
3
4
5
6
7
8
9
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
package com.tistory.itbaewom.instance;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
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.junit.jupiter.api.TestInstance.Lifecycle;
 
import com.tistory.itbaewom.util.Calculator;
 
 
@TestInstance(Lifecycle.PER_METHOD)
public class CalculatorTestPerMethod2 {
 
    private int result = 5;
 
    @Test
    void test_addition() {
        result = Calculator.addition(result, 2);
        System.out.println("Calculator.addition(result, 2) ===> " + result);
        assertEquals(7, result);
    }
 
    @BeforeAll // 새 테스트 인스턴스 생성시 1회
    void beforeAll() {
        System.out.println(">>>> @BeforeAll : 테스트를 시작합니다.\n");
    }
    
    @AfterAll // 새 테스트 인스턴스 소멸시 1회
    void afetrAll() {
        System.out.println(">>>> @AfterAll : 테스트를 종료합니다.");
    }
}
cs

 

 

테스트 수행 결과 입니다.

다음과 같은 에러가 발생합니다.

@BeforeAll method 'void com.tistory.itbaewom.instance.CalculatorTestPerMethod2.beforeAll()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).

 

 

위의 예제를 변경하여 @BeforeAll 과 @AfterAll 이 붙은 메서드를 static으로 변경해 보겠습니다.

 

1
2
3
4
5
6
7
8
9
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.tistory.itbaewom.instance;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
 
import com.tistory.itbaewom.util.Calculator;
 
 
@TestInstance(Lifecycle.PER_METHOD)
public class CalculatorTestPerMethod3 {
 
    private int result = 5;
 
    @Test
    void test_addition() {
        result = Calculator.addition(result, 2);
        System.out.println("Calculator.addition(result, 2) ===> " + result + "\n");
        assertEquals(7, result);
    }
 
    @Test
    void test_subtraction() {
        result = Calculator.subtraction(result, 2);
        System.out.println("Calculator.subtraction(result, 2) ===> " + result);
        assertEquals(3, result);
    }
    
    @BeforeAll // 새 테스트 인스턴스 생성시 1회
    static void beforeAll() {
        System.out.println(">>>> @BeforeAll : 테스트를 시작합니다.\n");
    }
    
    @AfterAll // 새 테스트 인스턴스 소멸시 1회
    static void afetrAll() {
        System.out.println(">>>> @AfterAll : 테스트를 종료합니다.");
    }
    
    @BeforeEach // 메서드 테스트 실행전 마다
    void beforeEach(TestInfo testInfo) {
        System.out.println("@BeforeEach : " + testInfo.getTestMethod().get().getName() + "메서드 테스트 수행시작!!!");
    }
    
    @AfterEach // 메서드 테스트 실행후 마다
    void afetrEach(TestInfo testInfo) {
        System.out.println("@AfterEach : " + testInfo.getTestMethod().get().getName() + "메서드 테스트 수행종료!!!\n");
    }
}
cs

 

테스트 수행 결과 입니다. 에러 없이 테스트가 수행이 됩니다.

 

 

콘솔창에 나타난 결과 입니다.

 

 

3-3. 기본적으로 Junit 5 중첩 테스트 클래스는 @BeforeAll 및 @AfterAll 메서드 작성을 허용하지 않습니다. 테스트 수명 주기 모드가 Lifecycle.PER_CLASS이고 비정적이어야 하는 경우에만 중첩 클래스에 작성할 수 있습니다.

 

1
2
3
4
5
6
7
8
9
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
package com.tistory.itbaewom.instance;
 
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
 
// 기본적으로 Junit 5 중첩 테스트 클래스는 @BeforeAll 및 @AfterAll 메서드 작성을 허용하지 않습니다. 
// 테스트 수명 주기 모드가 Lifecycle.PER_CLASS이고 비정적이어야 하는 경우에만 중첩 클래스에 작성할 수 있습니다.
public class Junit5NestedTestsTest {
 
    @Test
    void test1() {
        System.out.println(">>>> test1()");
    }
 
    // 다음 두줄을 지정하지 않으면 내부클래스 테스트 수행 안함
    @Nested
    @TestInstance(Lifecycle.PER_CLASS) 
    class TestA {
 
        @BeforeAll
        void testA_BeforeAll() {
            System.out.println("===> testA_BeforeAll()");
        }
        @Test
        void test1() {
            System.out.println("내부 클래스의 test1() 메서드 테스트 수행");
        }
        @AfterAll
        void testA_AfterAll() {
            System.out.println("===> testA_BeforeAll()");
        }
    }
}
cs

 

테스트 수행 결과 입니다. 에러 없이 테스트가 수행이 됩니다.

 

 

콘솔창에 나타난 결과 입니다. 

위의 코드를 보면 Lifecycle.PER_CLASS이고 비정적메서드 이어야 하는 경우에만 중첩 클래스에 작성할 수 있음을 알 수 있습니다.

 

 

 

3-4. 인터페이스 메서드에서 @BeforeAll 및 @AfterAll을 선언할 수 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.tistory.itbaewom.instance;
 
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
 
// 인터페이스 메서드에서 @BeforeAll 및 @AfterAll을 선언할 수 있습니다.
@TestInstance(Lifecycle.PER_CLASS)
public interface BaseTest {
 
    @BeforeAll
    default void beforeAll() {
        System.out.println(">>>> @BeforeAll : 테스트를 시작합니다.");
        System.out.println("-".repeat(80));
    }
 
    @AfterAll
    default void afetrAll() {
        System.out.println("-".repeat(80));
        System.out.println(">>>> @AfterAll : 테스트를 종료합니다.");
    }
}
cs

Java8에서 인터페이스에 디폴트 메소드(Default methods)라는 것이 추가되었습니다. 인터페이스는 메소드 정의만 할 수 있고 구현은 할 수 없었습니다만, Java8부터 디폴트 메소드라는 개념이 생겨 구현 내용도 인터페이스에 포함시킬 수 있었습니다. 메서드 선언시 default 예약어를 사용 합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.tistory.itbaewom.instance;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
 
import com.tistory.itbaewom.util.Calculator;
 
// 인터페이스 메서드에서 @BeforeAll 및 @AfterAll을 선언할 수 있습니다.
public class BaseTestImplIntsanceLifecycle implements BaseTest {
    private int result = 5;
 
    @Test
    void test_addition() {
        result = Calculator.addition(result, 2);
        System.out.println("Calculator.addition(result, 2) ===> " + result);
        assertEquals(5, result);
    }
 
    @Test
    void test_subtraction() {
        result = Calculator.subtraction(result, 2);
        System.out.println("Calculator.subtraction(result, 2) ===> " + result);
        assertEquals(3, result);
    }
}
cs

 

테스트 수행 결과 입니다. 에러 없이 테스트가 수행이 됩니다.

 

 

콘솔창에 나타난 결과 입니다. interface에 선언한  @BeforeAll 및 @AfterAll이 실행되었습니다.

 

 

 

4. 기본 테스트 인스턴스 수명 주기를 전체적으로 변경

기본 테스트 인스턴스 수명 주기 모드를 변경하려면 junit.jupiter.testinstance.lifecycle.default 구성 매개변수를 TestInstance.Lifecycle에 정의된 enum 상수의 이름으로 설정하고 대소문자는 무시합니다.

src/test/resources의 클래스 경로 루트에 junit-platform.properties라는 파일을 만듭니다.

테스트 인스턴스 수명 주기 모드를 per_class로 설정
-Djunit.jupiter.testinstance.lifecycle.default=per_class

테스트 인스턴스 수명 주기 모드를 per_method로 설정합니다(기본 동작이므로 실제로 설정할 필요 없음)
-Djunit.jupiter.testinstance.lifecycle.default=per_method

728x90