목차
1. JUnit 5 구조
| 구성 요소 | 설명 | 주요 클래스 / 의존성 |
| JUnit Platform | 실행 기반 플랫폼 | Launcher, Engine SPI |
| JUnit Jupiter | JUnit 5 API 및 실행 | junit-jupiter-api, junit-jupiter-engine |
| JUnit Vintage | JUnit 3, 4 호환 계층 | junit-vintage-engine + JUnit 4 |
1) JUnit Platform
- 테스트 실행을 위한 런타임 환경
- Jupiter든 Vintage든 모두 Platfrom위에서 실행
- 주요 모듈
모듈 설명 junit-platform-engine Jupiter, Vintage와 같은 엔진 구현을 위한 SPI 제공 junit-platform-launcher IDE, Gradle, Maven과 같은 실행기와 연결 역할 junit-platform-console 커맨드라인에서 JUnit 테스트 실행 가능하게 함 junit-platform-commons 공통 유틸리티 및 에러 처리 지원
2) JUnit Jupiter
- JUnit 5의 새로운 테스트 API
- 주요 구성
모듈 설명 junit-jupiter-api 테스트 작성에 필요한 애노테이션, API 제공 junit-jupiter-engine 테스트 실행 엔진 (Platform SPI 구현체) junit-jupiter-params 파라미터화 테스트 기능 제공 - 예제 코드
import org.junit.jupiter.api.*;
class CalculatorTest {
@BeforeAll
static void setupAll() {
System.out.println("✔ Before All - 클래스 전체에서 단 한 번 실행");
}
@BeforeEach
void setup() {
System.out.println("✔ Before Each - 각 테스트 전 실행");
}
@Test
void additionTest() {
Assertions.assertEquals(4, 2 + 2);
}
@AfterEach
void teardown() {
System.out.println("✔ After Each - 각 테스트 후 실행");
}
@AfterAll
static void teardownAll() {
System.out.println("✔ After All - 클래스 전체에서 단 한 번 실행");
}
}
- @BeforeAll, @AfterAll은 static이어야 하며 클래스 레벨에서 한 번만 실행됨
- @Test는 JUnit 5의 org.junit.**jupiter**.api.Test를 사용
3) JUnit Vintage
- Unit 3, 4 기반의 테스트를 JUnit 5 환경에서 실행가능하게 하는 호환 계층
- 예제
import org.junit.Test; // ✅ JUnit 4 전용 애너테이션
import static org.junit.Assert.*; // ✅ JUnit 4 전용 Assertion 클래스
public class LegacyTest {
@Test // ✅ JUnit 4 스타일
public void testOldWay() {
assertEquals(2, 1 + 1); // ✅ JUnit 4의 static import된 메서드 사용
}
}
- 의존성 추가
// build.gradle
dependencies {
testImplementation 'org.junit.vintage:junit-vintage-engine:5.9.3'
testImplementation 'junit:junit:4.13.2' // JUnit 4도 함께 필요함
}
- junit-vintage-engine을 추가하면 기존 JUnit 4 테스트를 유지하면서 JUnit 5로 점진적 전환 가능
2. 기본 어노테이션
| 애노테이션 | 설명 | 시스템 시작 시점 | 사용 변수 |
| @Test | 테스트 메서드와 같은 것을 결정 | 매 테스트 메서드에 대해 | - |
| @BeforeEach | 테스트 전에 실행 | 매 테스트 시작 전 | - |
| @AfterEach | 테스트 후에 실행 | 매 테스트 종료 후 | - |
| @BeforeAll | 전체 테스트 시작 전에 1회 | 한 클래스 시작전 | static or PER_CLASS |
| @AfterAll | 전체 테스트 종료 후 1회 | 한 클래스 종료 후 | static or PER_CLASS |
| @DisplayName | 테스트 명세화에 사용 | - | - |
1) @Test: 테스트 메서드 정의
- return type은 반드시 void
- 예외 던질 수 있음(= 실패 테스트 가능)
2) @BeforeEach / @AfterEach: 각 테스트 전/후에 실행
3) @BeforeAll / @AfterAll: 테스트 전체 전/후에 1회 실행
- static 메서드로 선언
- static 생략 위해 추가: @TestInstance(TestInstance.Lifecycle.PER_CLASS)
4) @DisplayName: 테스트 설명 지정
- 문서화 도구, 리포트 도구(JUnit HTML Report 등)에서 특히 유용
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
class DisplayNameTest {
@Test
@DisplayName("2과 3의 평균값은 2.5이고, 조금의 포인트가 있다")
void averageCalculationTest() {
double avg = (2 + 3) / 2.0;
assert avg == 2.5;
}
}
5) 실무 팁
- @BeforeEach/@AfterEach
- 테스트 간 상태 격리에 유리
- 테스트 환경을 초기화하거나, 로그를 정리하는 데 유용
- @BeforeAl/@AfterAll
- DB 연결 또는 외부 API 서버 세팅처럼 비용이 큰 작업을 한 번만 실행할 때 사용
- @DisplayName
- 협업 상황에서 테스트 의미를 명확히 전달하고자 할 때 필수적으로 사용
3. Assertion
: 특정 조건이 참(true)인지 확인하며, 조건이 만족되지 않으면 테스트를 실패로 처리
1) BDD와 Given-When-Then 스타일
- Given: 테스트에 필요한 입력 값 및 전제 조건 설정
- When: 테스트할 대상 메서드 호출
- Then: 기대한 결과를 Assertion으로 검증
2) 기본 검증 메서드
| 메서드 | 설명 |
| assertEquals(expected, actual) | 예상값과 실제값이 같은지 비교 |
| assertNotEquals(unexpected, actual) | 예상하지 않은 값과 다른지 비교 |
| assertTrue(condition) | 조건이 true인지 확 |
| assertFalse(condition) | 조건이 false인지 확인 |
| assertNull(object) | 객체가 null인지 확인 |
| assertNotNull(object) | 객체가 null이 아닌지 확인 |
- 예제
/**
* 기본적인 assertEquals, assertNotEquals, assertTrue 등을 활용한 단순 검증 테스트
*/
@Test
void basicAssertions() {
// given
int a = 2;
int b = 3;
// when
int result = a + b;
// then
assertEquals(5, result); // 예상값과 실제값이 같은지 확인
assertNotEquals(6, result); // 예상하지 않은 값과 다른지 확인
assertTrue(result > 0); // 조건이 true인지 확인
String value = null;
assertNull(value); // null 여부 확인
value = "Hello";
assertNotNull(value); // null이 아닌지 확인
}
3) 그룹 검증: assertAll()
: 여러 조건을 하나의 테스트 내에서 모두 검증하고자 할 때 사용
- 예제
/**
* assertAll을 활용하여 복수 조건을 한 번에 검증하는 테스트입니다.
*/
@Test
void groupAssertions() {
// given
String firstName = "Lee";
String lastName = "Jungmin";
// then
assertAll(
"이름 검증",
() -> assertEquals("Lee", firstName), // 이름 일치 여부 확인
() -> assertEquals("Jungmin", lastName), // 성 일치 여부 확인
() -> assertTrue(firstName.length() <= 5) // 길이 제한 확인
);
}
- 하나의 assertion이 실패해도 다른 assertion을 계속 실행하여 전체 검증 결과 제공
4) 예외 검증: assetThrows()
: 예외가 실제 발생해야만 테스트가 통과하며, 예외 메시지까지 검증하면 테스트 품질이 높아짐
- 예제
/**
* 예외가 발생해야 하는 상황을 테스트하는 assertThrows 사용 예시입니다.
*/
@Test
void exceptionTesting() {
// when & then
IllegalArgumentException thrown = assertThrows(
IllegalArgumentException.class,
() -> {
throw new IllegalArgumentException("잘못된 인자입니다.");
}
);
// 예외 메시지까지 함께 검증
assertEquals("잘못된 인자입니다.", thrown.getMessage());
}
5) BDD 스타일 예제
public class StampCalculator {
public static int calculateStampCount(int nowCount, int earned) {
return nowCount + earned;
}
public static int calculateEarnedStampCount(Order order) {
return order.getOrderCoffees().stream()
.map(OrderCoffee::getQuantity)
.mapToInt(Integer::intValue)
.sum();
}
}
// 테스트 클래스
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
// 테스트 대상 클래스: StampCalculator
public class StampCalculatorTest {
@Test
@DisplayName("현재 스탬프 수와 적립 스탬프 수를 더해 총합을 반환한다.")
void calculateStampCountTest() {
// given: 현재 스탬프 개수와 이번 주문으로 적립된 스탬프 수
int nowCount = 5;
int earned = 3;
// when: 스탬프 총합 계산
int actual = StampCalculator.calculateStampCount(nowCount, earned);
// then: 기대값은 8
int expected = 8;
assertEquals(expected, actual);
}
@Test
@DisplayName("주문에 포함된 모든 커피 수량을 합산해 적립 스탬프 수를 계산한다.")
void calculateEarnedStampCountTest() {
// given: 커피 2잔 각각 수량 3, 5로 설정된 주문 생성
Order order = new Order();
OrderCoffee coffee1 = new OrderCoffee();
coffee1.setQuantity(3);
OrderCoffee coffee2 = new OrderCoffee();
coffee2.setQuantity(5);
order.setOrderCoffees(List.of(coffee1, coffee2));
// when: 적립 스탬프 수 계산
int actual = StampCalculator.calculateEarnedStampCount(order);
// then: 기대값은 3 + 5 = 8
int expected = 8;
assertEquals(expected, actual);
}
}
6) 정리
| 기능 | 메서드 | 예시 |
| 값 비교 | assertEquals, assertNotEquals | assertEquals(5, result) |
| 조건 확인 | assertTrue, assertFalse | assertTrue(a > 0) |
| null 확인 | assertNull, assertNotNull | assertNotNull(obj) |
| 그룹 검증 | assertAll | 여러 조건을 한 번에 검사 |
| 예외 검증 | assertThrows | assertThrows(IllegalArgumentException.class, ...) |
| BDD 스타일 | given-when-then | 테스트 목적 가독성 향상 |
4. 테스트 라이프사이클
1) 테스트 인스턴스의 생명주기
- PER_METHOD: 기본적으로 테스트 메서드마다 새로운 인스턴스 생성
- PER_CLASS: 테스트 메서드 간 상태 공유가 필요한 경우, 클래스 단위로 한 번만 인스턴스 생성
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SampleLifecycleTest {
private int counter = 0;
@Test
void test1() {
counter++;
System.out.println("test1 실행 후 counter = " + counter);
}
@Test
void test2() {
counter++;
System.out.println("test2 실행 후 counter = " + counter);
}
}
// PER_CLASS
test1 실행 후 counter = 1
test2 실행 후 counter = 2
// PER_METHOD
test1 실행 후 counter = 1
test2 실행 후 counter = 1
2) 테스트 전후 후킹 메서드 (Setup/Teardown)
: 테스트 실행 전후에 특정 메서드를 자동으로 실행해주는 후킹(Hook) 애노테이션
| 애노테이션 | 실행 시점 | static 여부 | PER_CLASS 필요 여부 | 설명 |
| @BeforeAll | 모든 테스트 실행 전 1회 | 기본적으로 static이어야 함 | PER_CLASS일 경우 static 생략 가능 | 테스트 환경 초기화용 |
| @BeforeEach | 각 테스트 직전마다 | static 아님 | 필요 없음 | 공통 준비 작업 수행 |
| @AfterEach | 각 테스트 직후마다 | static 아님 | 필요 없음 | 리소스 정리, 로그 등 |
| @AfterAll | 모든 테스트 실행 후 1회 | 기본적으로 static이어야 함 | PER_CLASS일 경우 static 생략 가능 | 리소스 반환, 마무리 처리 등 |
- 후킹 메서드 활용 전략
목적 활용 애노테이션 설명 DB 연결 및 공용 자원 초기화 @BeforeAll 테스트 전 단 한 번만 설정 공통 입력값, 객체 준비 @BeforeEach 각 테스트에서 독립적으로 활용 로그 출력, 리소스 닫기 @AfterEach 메모리 누수 방지에 효과적 DB 연결 해제, 종료 로그 @AfterAll 전체 테스트 종료 후 정리 작업
3) 테스트 순서 결정
: 테스트 간 의존성을 방지하기 위해, 테스트 메서드의 실행 순서 보장 X
- 순서를 명시하고 싶은 경우
애노테이션 설명 @TestMethodOrder(...) 테스트 메서드의 실행 순서를 정의 @Order(int) 실행 순서를 지정할 숫자를 설정 (낮을수록 먼저 실행됨) - 순서 사용 시점
상황 권장 여부 설명 단위 테스트(Unit Test) 비권장 독립적인 실행이 원칙. 순서에 의존하지 말아야 함 통합 테스트(Integration Test) 제한적 허용 복잡한 준비-검증-정리 과정에서는 순서를 강제하는 경우 존재 데이터베이스 시나리오 테스트 가능 예: 사용자 생성 → 수정 → 삭제 순서 검증이 필요한 경우
4) 테스트 그룹화: Nested 개발
: 테스트 클래스 내에서 내부 클래스를 사용해 관련 테스트들을 그룹화
- 유사 기능끼리 묶어서 보기 쉽게 만들 수 있음
- 각 @Nested 내부에도 @BeforeEach, @AfterEach 등을 별도 정의 가능
- 계층적 구조로 테스트 코드 관리 가능
class UserServiceTest {
@Nested
class LoginTest {
@BeforeEach
void setupLogin() {
System.out.println("[Login 준비]");
}
@Test
void successLogin() {
System.out.println("로그인 성공");
}
}
@Nested
class SignupTest {
@BeforeEach
void setupSignup() {
System.out.println("[Signup 준비]");
}
@Test
void successSignup() {
System.out.println("회원가입 성공");
}
}
}
// 실행 결과
[Login 준비]
로그인 성공
[Signup 준비]
회원가입 성공728x90
'Software Engineering > Test & Quality' 카테고리의 다른 글
| [TDD] 4. Mockito (0) | 2025.08.19 |
|---|---|
| [TDD] 3. Spring Data JPA: 데이터 액세스 계층 테스트 (1) | 2025.08.18 |
| 상황에 맞는 예외 정리 (4) | 2025.08.14 |
| [TDD] 1-2. Spring Test 모듈 (2) | 2025.08.13 |
| [TDD] 1-1. Spring Test (1) | 2025.08.13 |