7 Popular Unit Test Naming Conventions - 좋은 단위테스트 이름 짓기

StrayCat·2026년 3월 6일

CS지식

목록 보기
12/32

단위 테스트(Unit Test)를 처음 작성하다 보면 기능 구현보다 테스트 메서드 이름 짓기에서 더 많은 고민을 하게 되는 경우가 있다.

Roy Osherove의 The Art of Unit Testing 과 Ajitesh Kumar의 "7 Popular Unit Test Naming Conventions" 등 유명한 자료들을 바탕으로 정리했다.

테스트 이름은 단순한 레이블이 아니다. 테스트가 실패했을 때, 코드를 열어보지 않고도 무엇이 왜 실패했는지 파악할 수 있어야 한다. 그것이 좋은 테스트 이름의 핵심이다.


왜 네이밍이 중요한가

// 나쁜 예
@Test
void test1() { ... }

@Test
void withdrawTest() { ... }
// 좋은 예
@Test
void withdraw_InsufficientBalance_ThrowsException() { ... }

테스트가 수백 개 쌓였을 때, CI/CD 빌드 서버의 로그에서 실패한 테스트를 추적해야 한다고 생각해보자. 이름 하나가 디버깅 시간을 크게 단축시켜 줄 수 있다.


좋은 테스트 이름이 담아야 할 3가지 요소

대부분의 네이밍 컨벤션은 아래 3가지 요소를 어떤 순서, 어떤 형태로 배치하느냐의 차이다.

요소의미예시
Unit of Work테스트 대상 메서드/기능withdraw
State Under Test테스트 시점의 상태/조건InsufficientBalance
Expected Behavior기대하는 결과ThrowsException

7가지 네이밍 컨벤션

아래 예시는 모두 동일한 시나리오를 다룬다.

시나리오: 계좌 잔액이 부족할 때 출금을 시도하면 예외가 발생해야 한다.


1. 메서드명_테스트상태_기대결과

MethodName_StateUnderTest_ExpectedBehavior

Roy Osherove가 The Art of Unit Testing에서 제안한 방식으로, 가장 널리 알려진 컨벤션이다.

@Test
void withdraw_InsufficientBalance_ThrowsException() {
    // Arrange
    Account account = new Account(100);

    // Act & Assert
    assertThrows(InsufficientBalanceException.class,
        () -> account.withdraw(200));
}

장점: 메서드명과 상태, 결과가 명확히 분리된다.

단점: 리팩토링으로 메서드 이름이 바뀌면 테스트 이름도 함께 갱신해야 한다.


2. 메서드명_기대결과_테스트상태

MethodName_ExpectedBehavior_StateUnderTest

1번의 순서를 약간 바꾼 변형이다. 일부 팀에서는 결과를 먼저 보고 싶을 때 이 순서를 선호한다.

@Test
void withdraw_ThrowsException_WhenBalanceIsInsufficient() {
    Account account = new Account(100);

    assertThrows(InsufficientBalanceException.class,
        () -> account.withdraw(200));
}

단점: 1번과 동일하게 메서드명 변경 시 유지보수 부담이 있다.


3. test[테스트할기능]

test[FeatureBeingTested]

테스트 프레임워크의 어노테이션(@Test)이 이미 "이것은 테스트다"라는 것을 나타내므로, 메서드 이름에는 기능 자체만 담는다는 철학이다.

@Test
void testWithdrawWithInsufficientBalance() {
    Account account = new Account(100);

    assertThrows(InsufficientBalanceException.class,
        () -> account.withdraw(200));
}

참고: JUnit 5부터는 test 접두사 없이도 @Test만으로 충분하므로, 이 방식의 test 접두사는 다소 중복된 표현으로 여겨지기도 한다.


4. 테스트할기능 (접두사 없음)

FeatureBeingTested (접두사 없음)

3번에서 test 접두사조차 제거한 방식이다. JUnit 5의 @DisplayName과 조합하면 매우 읽기 좋은 테스트 결과를 만들 수 있다.

@Test
@DisplayName("잔액이 부족한 경우 출금 시 예외가 발생한다")
void withdrawWithInsufficientBalance() {
    Account account = new Account(100);

    assertThrows(InsufficientBalanceException.class,
        () -> account.withdraw(200));
}

JUnit 5의 @DisplayName은 한글 설명도 가능하므로, 메서드명은 간결하게 유지하면서 가독성을 높이는 방식으로 많이 활용된다.


5. Should_기대결과_When_테스트상태

Should_ExpectedBehavior_When_StateUnderTest

"~해야 한다"는 표현으로 요구사항을 자연어에 가깝게 서술하는 방식이다.

@Test
void Should_ThrowException_When_BalanceIsInsufficient() {
    Account account = new Account(100);

    assertThrows(InsufficientBalanceException.class,
        () -> account.withdraw(200));
}

장점: 사람이 읽기 자연스럽고, 요구사항 문서처럼 읽힌다.


6. When_테스트상태_Expect_기대결과

When_StateUnderTest_Expect_ExpectedBehavior

"~한 상태일 때, ~를 기대한다"는 구조로, 조건이 먼저 오는 형태다.

@Test
void When_BalanceIsInsufficient_Expect_ExceptionThrown() {
    Account account = new Account(100);

    assertThrows(InsufficientBalanceException.class,
        () -> account.withdraw(200));
}

7. Given_전제조건_When_테스트상태_Then_기대결과

Given_Preconditions_When_StateUnderTest_Then_ExpectedBehavior

BDD(Behavior-Driven Development) 의 Given-When-Then 패턴에서 유래한 방식이다. 가장 서술적이며, 비개발자도 이해하기 쉬운 구조를 지향한다.

@Test
void Given_AccountWith100_When_WithdrawingMore_Then_ThrowsException() {
    // Given
    Account account = new Account(100);

    // When & Then
    assertThrows(InsufficientBalanceException.class,
        () -> account.withdraw(200));
}

장점: 테스트가 요구사항 명세서처럼 기능한다. Cucumber, JBehave 같은 BDD 프레임워크와 철학적으로 잘 맞는다.

단점: 이름이 길어질 수 있다.


7가지 비교 요약

#패턴스타일추천 상황
1Method_State_Behavior기술적일반적인 단위 테스트, 레거시 코드베이스
2Method_Behavior_State기술적결과 중심 팀
3testFeature단순소규모 프로젝트
4featureDescription간결JUnit 5 + @DisplayName 조합
5Should_Behavior_When_State서술적요구사항 중심 개발
6When_State_Expect_Behavior서술적조건 중심 서술 선호 팀
7Given_When_ThenBDDBDD 팀, 기획자와 협업 시

현업에서는 어떻게 선택하나

이미 구축된 레거시 코드베이스에서는 팀 내 기존 컨벤션을 따르는 것이 최우선이다. 새 프로젝트라면 아래 기준으로 선택을 권장한다.

  • 순수 Java 백엔드, 팀 내 개발자만 보는 경우 → 패턴 1 또는 4 (@DisplayName 활용)

  • BDD를 도입하거나, 비개발자도 테스트 명세를 확인하는 경우 → 패턴 7

  • Spring Boot 기반 REST API 테스트 → 패턴 5 또는 7

  • 위 7가지 중 현재 Java/Spring Boot 생태계에서 가장 많이 보이는 패턴은 1번과 4번(+ @DisplayName)

가장 중요한 원칙은 팀 내 일관성이다. 어떤 컨벤션을 선택하든, 프로젝트 전체에서 동일하게 적용하는 것이 무엇보다 우선이다.


추가 권고사항

JUnit 5의 @DisplayName 적극 활용

@Nested
@DisplayName("출금(withdraw) 기능")
class WithdrawTest {

    @Test
    @DisplayName("잔액이 부족하면 예외를 던진다")
    void throwsExceptionWhenBalanceInsufficient() {
        Account account = new Account(100);
        assertThrows(InsufficientBalanceException.class,
            () -> account.withdraw(200));
    }

    @Test
    @DisplayName("잔액이 충분하면 출금 후 잔액이 감소한다")
    void decreasesBalanceWhenSufficientFunds() {
        Account account = new Account(100);
        account.withdraw(50);
        assertEquals(50, account.getBalance());
    }
}

JUnit 5의 @Nested + @DisplayName 조합은 계층적 테스트 구조를 만들 수 있어, 메서드 이름의 가독성 부담을 크게 줄여준다. 현재 Java 생태계에서 가장 권장되는 방식 중 하나이다.

AssertJ의 활용

// JUnit 기본 assert 대신
assertEquals(50, account.getBalance());

// AssertJ 스타일 (더 읽기 쉬움)
assertThat(account.getBalance()).isEqualTo(50);
assertThat(account.getBalance()).isGreaterThan(0).isLessThan(100);

네이밍과 더불어, AssertJ를 활용하면 테스트 코드 자체의 가독성도 크게 향상된다.


마무리

좋은 테스트 이름은 코드 문서화의 역할을 한다. 테스트가 실패했을 때 이름만 보고도 "무엇이, 어떤 조건에서, 어떻게 실패했는지"를 알 수 있다면 그것이 좋은 네이밍이다.

네이밍 컨벤션을 새로 도입한다면, 팀원들과 합의하여 하나를 선택하고 일관되게 적용하는 것을 권장한다.

profile
알면 좋은 것보단 잊어버리기 싫은 것들을 기록합니다.

0개의 댓글