학습목표
JUnit 테스트 생명주기 이해하기
@BeforeAll 메서드는 테스트 인스턴스 생성전에 실행되어야 하기 때문에 static이 붙은 게 이해가 됐는데, 왜 @AfterAll까지 static이 붙어야할까? 라는 의문지 들어서 JUnit 테스트의 생명주기에 대해 알아보게 되었다.
1. 예제
package com.springboot.jpa;
import org.junit.jupiter.api.*;
public class TestLifeCycle {
@BeforeAll
static void beforeAll() {
System.out.println("## BeforeAll Annotation 호출 ##");
}
@AfterAll
static void afterAll() {
System.out.println("## AfterAll Annotation 호출 ##");
}
@BeforeEach
void beforeEach() {
System.out.println("## BeforeEach Annotation 호출 ##");
}
@AfterEach
void afterEach() {
System.out.println("## AfterEach Annotation 호출 ##");
}
@Test
void test1() {
System.out.println("test1 실행");
}
@Test
void test2() {
System.out.println("test2 실행");
}
}
2. 실행 순서(결과)
JUnit은 위 클래스를 실행할 때 다음 순서로 동작한다.
BeforeAll
BeforeEach
test1
AfterEach
BeforeEach
test2
AfterEach
AfterAll
즉,
- @BeforeAll → 전체 테스트 시작 전에 한 번 실행
- @BeforeEach → 각 테스트 실행 전에 실행
- @AfterEach → 각 테스트 실행 후에 실행
- @AfterAll → 모든 테스트가 끝난 뒤 한 번 실행
3. 테스트 인스턴스란?
테스트 인스턴스(Test Instance)란 테스트 클래스의 객체로 new TestLifeCycle()로 생성된 하나의 객체를 의미한다.
JUnit은 기본적으로 각 테스트 메서드(@Test)마다 새로운 인스턴스를 생성해서 테스트를 실행한다.
JUnit의 테스트 실행방법
for (테스트 메서드 : @Test 메서드 목록) {
new TestLifeCycle() // ① 인스턴스 생성
실행할 객체 안에 @BeforeEach가 있나 확인 → 있다면 실행 // ② BeforeEach
@Test 메서드 실행 // ③ Test
실행할 객체 안에 @AfterEach가 있나 확인 → 있다면 실행 // ④ AfterEach
인스턴스 폐기 // ⑤ 객체 제거
}
실제 실행 예시
--- test1 실행 시 ---
new TestLifeCycle() ← 인스턴스 A
A.beforeEach()
A.test1()
A.afterEach()
(인스턴스 A 폐기)
--- test2 실행 시 ---
new TestLifeCycle() ← 인스턴스 B
B.beforeEach()
B.test2()
B.afterEach()
(인스턴스 B 폐기)
이렇게 하면 테스트 간에 상태가 공유되지 않기 때문에 독립성이 유지된다.
🧬 4. 왜 @BeforeAll과 @AfterAll은 static이어야 할까?
JUnit은 테스트를 실행하기 전에 다음 과정을 거친다.
- 테스트 클래스 스캔 → 어떤 메서드에 @BeforeAll, @Test, @AfterAll이 붙었는지 확인
- @BeforeAll 실행
- 각 테스트(@Test) 실행
- @AfterAll 실행
문제는 2번 단계에서 아직 new TestLifeCycle()이 실행되지 않았다는 점이다.
즉, 인스턴스가 존재하지 않기 때문에 인스턴스 메서드를 호출할 수 없다.
그래서 클래스 레벨에서 직접 호출할 수 있도록
@BeforeAll, @AfterAll은 반드시 static이어야 한다. (테스트가 종료된 후에는 인스턴스가 삭제되므로 인스턴스 메서드를 호출할 수 없어서 @AfterAll도 static이어야 한다.)
@BeforeAll
static void beforeAll() { ... } //가능
@BeforeAll
void beforeAll() { ... } //인스턴스가 없으므로 불가능
5. @BeforeEach와 @AfterEach는 인스턴스 기반
반면 @BeforeEach, @AfterEach는
각 테스트가 실행될 때마다 생성된 인스턴스 내부에서 동작한다.
즉, @BeforeEach → @Test → @AfterEach는 하나의 TestLifeCycle 객체 안에서 수행된다.
(new TestLifeCycle) ← 생성
beforeEach()
testX()
afterEach()
(인스턴스 삭제)
6. 확인용 예제
public class TestLifeCycle {
private int counter = 0;
@BeforeEach
void beforeEach() {
System.out.println("BeforeEach: counter=" + counter);
}
@Test
void test1() {
counter++;
System.out.println("test1: counter=" + counter);
}
@Test
void test2() {
counter++;
System.out.println("test2: counter=" + counter);
}
@AfterEach
void afterEach() {
System.out.println("AfterEach: counter=" + counter);
}
}
출력 결과
BeforeEach: counter=0
test1: counter=1
AfterEach: counter=1
BeforeEach: counter=0
test2: counter=1
AfterEach: counter=1
각 테스트가 독립된 인스턴스에서 실행되기 때문에 counter 값이 항상 0에서 시작한다.
7. PER_CLASS 모드 — 인스턴스 1개만 쓰는 방법
JUnit 5에서는 다음 설정으로 테스트 인스턴스를 하나만 만들 수 있다.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestLifeCycle {
@BeforeAll
void beforeAll() { ... } // static이 아니어도 가능
}
이 경우에는 클래스 인스턴스가 한 번만 생성되고
모든 테스트가 같은 객체를 공유한다.
new TestLifeCycle() ← 한 번만 생성
beforeAll()
(beforeEach → test1 → afterEach)
(beforeEach → test2 → afterEach)
afterAll()
이때는 @BeforeAll, @AfterAll이 static이 아니어도 된다.
오늘의 코멘트
처음엔 단순히 “왜 static이어야 하지?” 정도의 의문이었는데, 결국 핵심은 테스트 인스턴스의 생명주기를 이해하는 데 있었다.
@Test 메서드 하나당 새로운 인스턴스가 만들어지고 그 인스턴스 안에서 BeforeEach → Test → AfterEach가 한 싸이클로 돈다.
@BeforeAll, @AfterAll는 인스턴스가 생성되기 이전, 삭제된 이후에 실행되어야 하기 때문에 static이어야 한다.
'SPRING > Test' 카테고리의 다른 글
| 250925(목) 단위테스트 (0) | 2025.09.25 |
|---|