어제 오늘 내일

[JUnit] @Nested를 이용한 테스트 코드 구조화 패턴 본문

IT/JUnit

[JUnit] @Nested를 이용한 테스트 코드 구조화 패턴

hi.anna 2026. 1. 9. 01:25

1. @Nested

@Nested는 JUnit 5에서 테스트 코드를 논리적으로 그룹화하기 위해 제공되는 애너테이션입니다.
한 클래스 안에서 상황(조건)별로 테스트를 묶을 수 있어서, 테스트가 많아져도 구조를 명확하게 유지할 수 있습니다.

주요 특징입니다.

  • 외부 테스트 클래스 안에 내부 클래스를 만들어 그룹을 나눕니다.
  • 각 내부 클래스에 @Nested를 붙여 **“이 상황에서의 테스트 묶음”**을 표현합니다.
  • @DisplayName과 함께 사용하면 테스트가 문서처럼 읽힙니다.
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

class UserServiceTest {

    @Nested
    class 회원가입_기능 {

        @Test
        void 정상_회원가입() {
            // 검증 코드
        }

        @Test
        void 중복_아이디로_실패() {
            // 검증 코드
        }
    }
}

 

2. 상태/상황별로 테스트를 그룹화하는 기본 패턴

아래 예제는 계좌(Account)의 상태에 따라 테스트를 나누는 패턴입니다.

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class AccountTest {

    @Nested
    class 잔액이_0원인_상태 {

        @Test
        void 출금하면_실패합니다() {
            int balance = 0;
            int withdraw = 100;
            assertTrue(withdraw > balance);
        }

        @Test
        void 입금하면_잔액이_증가합니다() {
            int balance = 0;
            int deposit = 100;
            assertEquals(100, balance + deposit);
        }
    }

    @Nested
    class 잔액이_양수인_상태 {

        @Test
        void 출금하면_잔액이_감소합니다() {
            int balance = 1000;
            int withdraw = 200;
            assertEquals(800, balance - withdraw);
        }
    }
}

설명

  • “잔액이 0원인 상태”, “잔액이 양수인 상태”처럼 상태 기준으로 그룹을 나눕니다.
  • 각 그룹 안에는 그 상태에서 가능한 행동/결과에 대한 테스트가 모여 있습니다.
  • 테스트를 읽는 것만으로 도메인 규칙을 이해하기 쉬워집니다.

 

3. @BeforeEach와 함께 사용하는 패턴

내부 클래스마다 공통 준비 코드를 둘 수도 있습니다.
상황별로 다른 초기 상태를 만들 때 유용합니다.

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class BankAccountTest {

    static class BankAccount {
        private int balance;

        BankAccount(int balance) {
            this.balance = balance;
        }

        void deposit(int amount) {
            balance += amount;
        }

        void withdraw(int amount) {
            balance -= amount;
        }

        int getBalance() {
            return balance;
        }
    }

    @Nested
    class 초기_잔액이_1000원인_계좌 {

        BankAccount account;

        @BeforeEach
        void setUp() {
            account = new BankAccount(1000);
        }

        @Test
        void 입금하면_잔액이_증가합니다() {
            account.deposit(500);
            assertEquals(1500, account.getBalance());
        }

        @Test
        void 출금하면_잔액이_감소합니다() {
            account.withdraw(300);
            assertEquals(700, account.getBalance());
        }
    }

    @Nested
    class 초기_잔액이_0원인_계좌 {

        BankAccount account;

        @BeforeEach
        void setUp() {
            account = new BankAccount(0);
        }

        @Test
        void 출금하면_마이너스가_됩니다() {
            account.withdraw(100);
            assertEquals(-100, account.getBalance());
        }
    }
}

설명

  • 각 @Nested 클래스 안에 @BeforeEach를 두어 그 그룹만의 초기 상태를 만듭니다.
  • 같은 BankAccount라도 “초기 잔액이 1000원”, “초기 잔액이 0원”을 분리해서 더 명확하게 표현합니다.

 

4. @DisplayName과 함께 테스트를 문서처럼 만들기

@Nested와 @DisplayName을 같이 사용하면 테스트 결과를 시나리오 문서처럼 읽을 수 있습니다.

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

@DisplayName("회원 서비스 테스트")
class UserServiceTest {

    @Nested
    @DisplayName("회원가입 기능")
    class SignUpTests {

        @Test
        @DisplayName("정상 정보로 회원가입에 성공한다")
        void signUp_success() {
            boolean result = true;
            assertTrue(result);
        }

        @Test
        @DisplayName("이미 존재하는 아이디로는 회원가입에 실패한다")
        void signUp_fail_when_duplicate_id() {
            boolean result = false;
            assertFalse(result);
        }
    }

    @Nested
    @DisplayName("로그인 기능")
    class LoginTests {

        @Test
        @DisplayName("올바른 아이디와 비밀번호로 로그인에 성공한다")
        void login_success() {
            boolean result = true;
            assertTrue(result);
        }
    }
}

설명

  • 실행 결과에
    • “회원 서비스 테스트 > 회원가입 기능 > 정상 정보로 회원가입에 성공한다”
      같은 형태로 표시되어, 테스트가 설명서처럼 읽히는 구조가 됩니다.

 

5. @Nested 사용 시 패턴과 팁

  1. 상태·상황 기준으로 나눈다
    “로그인된 상태”, “로그인되지 않은 상태”, “재고가 있는 상품”, “재고가 없는 상품”처럼
    도메인에서 중요한 상태를 기준으로 @Nested 클래스를 나누면 이해하기 쉽습니다.
  2. 테스트 메소드 이름은 짧게, @DisplayName은 설명형으로
    • 메소드: login_success()
    • DisplayName: "올바른 아이디와 비밀번호로 로그인에 성공한다"
      와 같이 분리하면 코드와 문서 역할을 나눌 수 있습니다.
  3. 중첩 깊이를 너무 깊게 만들지 않는다
    2~3단계를 넘기면 오히려 복잡해질 수 있으므로, 상위 수준의 구조만 @Nested로 표현하는 것이 좋습니다.
  4. 공통 준비 코드는 각 @Nested 클래스 내부에 둔다
    상황마다 초기 상태가 다르므로, @BeforeEach를 각 내부 클래스에서 정의하면 테스트가 서로 영향을 주지 않습니다.

 

6. 정리

@Nested는 테스트를 상황·상태별로 구조화할 수 있게 해 주는 기능입니다.
@BeforeEach, @DisplayName과 함께 사용하면 테스트 코드가

  • 읽기 쉽고
  • 유지보수하기 좋고
  • 도메인 규칙을 그대로 드러내는
    문서 같은 테스트로 바뀝니다.

 

 

반응형
Comments