본문 바로가기
Backend/Junit5

[Junit5] Junit5 정리 1 - Junit5에서 제공하는 어노테이션

by 제이동 개발자 2025. 8. 10.
728x90

[Junit5] Junit5 정리 1 - Junit5에서 제공하는 어노테이션

JUnit 5는 크게 JUnit Platform(실행 기반), JUnit Jupiter(새 테스트 API/엔진), JUnit Vintage(JUnit4 호환)로 구성됩니다. 실무에서 우리가 쓰는 어노테이션은 대부분 Jupiter에 있습니다.

  • JUnit Platform: 테스트 실행 플랫폼(런너).
  • JUnit Jupiter: JUnit 5 API와 엔진(우리가 쓰는 어노테이션 대부분).
  • JUnit Vintage: JUnit4 테스트 실행 호환.

표 정리

어노테이션 역할
@Test 일반 테스트
@DisplayName 테스트 이름 꾸미기
@Disabled 임시 비활성화
@Timeout 시간 제한
@RepeatedTest 반복 실행
   
@BeforeAll / @AfterAll 전체 전/후 1회
@BeforeEach / @AfterEach 각 테스트 전/후
@Nested 테스트 계층화
@TestInstance 인스턴스 라이프사이클
@TestMethodOrder / @Order 실행 순서 지정
@Tag 태깅/필터링
@ParameterizedTest 파라미터화 테스트
@TestFactory 동적 테스트
@ExtendWith 확장 등록

 

 

1. @Test

  • 역할: 표준 테스트 메서드 지정
  • 적용 위치: 메서드
  • 언제: 단위/슬라이스/통합 전반
  • 실무 팁: 메서드명 행위_should결과; 실패 메시지는 구체적으로
  • 특징/제약
    • 가시성 public 강제 아님(패키지 프라이빗/protected도 OK)
    • @Test와 @ParameterizedTest는 동시에 사용 불가
    • 예외 기대 시 assertThrows/assertDoesNotThrow 사용
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

  // @Test: 단위 테스트의 기본 어노테이션
  @Test
  void add_shouldReturnSum() {
    // given
    int a = 2, b = 3;

    // when
    int result = a + b;

    // then
    assertEquals(5, result); // 2 + 3 = 5 검증
  }
}

 

 

2. @DisplayName / @DisplayNameGeneration

  • 역할: 테스트/클래스 이름을 사람이 읽기 좋게
  • 적용 위치: 클래스, 메서드
  • 언제: CI 로그/리포트 가독성 중요할 때
  • 실무 팁: 요구사항/티켓 ID 포함(추적성↑)
  • 특징/제약
    • @DisplayName이 있으면 @DisplayNameGeneration보다 우선
    • built-in 제너레이터(예: ReplaceUnderscores, IndicativeSentences) 활용
import org.junit.jupiter.api.*;

@DisplayName("계산기 모듈 테스트")
class DisplayNameExampleTest {

  @Test
  @DisplayName("1) 양수+양수 덧셈은 정상 동작")
  void testAddPrettyName() {}
}

 

 

 

3. @Disabled

  • 역할: 테스트/클래스 임시 비활성화
  • 적용 위치: 클래스, 메서드
  • 언제: 외부 의존 장애, 릴리즈 직전 임시 제외
  • 실무 팁: 사유/재활성 조건(티켓 번호 등) 반드시 기록
  • 특징/제약
    • 조건부 비활성은 @Disabled*(환경/OS 등) 계열 사용 권장
import org.junit.jupiter.api.*;

@Disabled("릴리즈 2.3에서 다시 활성화 예정 (JIRA-1234)")
class DisabledExampleTest {

  @Test
  void willNotRun() {}
}

 

 

4. @Timeout

  • 역할: 최대 수행 시간 제한
  • 적용 위치: 클래스, 메서드
  • 언제: IO/DB/외부 API 의존·성능 가드레일
  • 실무 팁: 통합 테스트에 보수적으로 적용, 타임아웃 발생 시 로깅 강화
  • 특징/제약
    • 타임아웃은 테스트 스레드를 인터럽트하는 방식이라, 네이티브/블로킹 호출은 즉시 중단이 보장되지 않을 수 있음
    • 코드 블록 단위 제어가 필요하면 assertTimeout(…)/assertTimeoutPreemptively(…) 대안 고려
import org.junit.jupiter.api.*;
import java.util.concurrent.TimeUnit;

class TimeoutTest {
  @Test
  @Timeout(value = 800, unit = TimeUnit.MILLISECONDS)
  void shouldFinishUnder800ms() throws Exception { Thread.sleep(100); }
}

 

 

5. @RepeatedTest

  • 역할: 동일 테스트 N회 반복
  • 적용 위치: 메서드
  • 언제: 비결정성/레이스 조건/캐시 워밍업 검증
  • 실무 팁: 랜덤 관련 테스트는 시드 고정으로 재현성 확보
  • 특징/제약
    • @Test와 병행 사용 불가(대체 관계)
    • 반복 인덱스를 파라미터 주입(RepetitionInfo)로 받을 수 있음
import org.junit.jupiter.api.RepeatedTest;

class RepeatExample {
  @RepeatedTest(5)
  void flaky_shouldStabilize() { /* ... */ }
}

 

 

6. @TestMethodOrder / @Order

  • 역할: 테스트 실행 순서 지정
  • 적용 위치: 클래스(@TestMethodOrder), 메서드(@Order)
  • 언제: 시나리오/마이그레이션 순차 검증(단위 테스트에선 지양)
  • 실무 팁: 순서 의존은 테스트 독립성 저해하므로 최소화 해야 함
  • 특징/제약
    • 다양한 Orderer 제공(Annotation, DisplayName, MethodName, Random)
    • 순서 결정은 엔진/런타임에 의존 → 결과에 존 로직 두지 말 것
import org.junit.jupiter.api.*;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedExample {
  @Test @Order(1) void step1() {}
  @Test @Order(2) void step2() {}
}

 

 

7. @TestInstance

  • 역할: 테스트 클래스 인스턴스 라이프사이클
  • 적용 위치: 클래스
  • 언제: 생성 비용이 비쌀경우 사용, 그 외는 @BeforeAll/@AfterAll을 인스턴스 메서드로 사용
    • PER_METHOD : 기본 값, 테스트 메서드마다 새 인스턴스를 만들고 실행(테스트 간 상태 고립 ↑)
    • PER_CLASS : 테스트 클래스당 인스턴스 하나로 모든 테스트 메서드를 실행(공유 상태 가능, 비싼 초기화 1회만)
  • 실무 팁: PER_CLASS는 상태 공유 부작용 주의(필드 변경 최소화)
  • 특징/제약
    • PER_CLASS일 때만 비-static @BeforeAll/@AfterAll 허용
import org.junit.jupiter.api.*;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PerClassExample {
  @BeforeAll void initOnce() { /* 비싼 초기화 */ }
}

 

 

8. @BeforeAll / @AfterAll

  • 역할: 전체 테스트 전/후 1회
  • 적용 위치: 메서드
  • 언제: 컨테이너/서버 기동, 대량 픽스처 로드/정리
  • 실무 팁: 리소스 누수 방지(정상 종료 보장), 로그로 소요 시간 측정
  • 특징/제약
    • 기본 라이프사이클(PER_METHOD)에서는 static 필요
    • @Nested에서 사용 제약 존재 → 상위에서 처리하거나 PER_CLASS 고려
import org.junit.jupiter.api.*;

class LifecycleExampleTest {

  @BeforeAll
  static void initAll() {
    // 전체 테스트에 공통 1회 초기화 (예: WireMock 서버 부팅)
  }

  @AfterAll
  static void tearDownAll() {
    // 전체 테스트 종료 후 1회 정리
  }

  @Test
  void sample() {}
}

 

 

9. @BeforeEach / @AfterEach

  • 역할: 각 테스트 직전/직후
  • 적용 위치: 메서드
  • 언제: 트랜잭션 시작/롤백, 공통 픽스처 준비/정리
  • 실무 팁: 격리 보장(공유 상태 제거, 테스트 간 영향 차단)
  • 특징/제약
    • 예외 발생 시 해당 테스트는 실패로 집계, 후속 훅/테스트 흐름에 영향
import org.junit.jupiter.api.*;

class LifecycleExampleTest {

  @BeforeEach
  void initEach() {
    // 각 테스트 전에 공통 준비 (예: 트랜잭션 시작)
  }

  @AfterEach
  void tearDownEach() {
    // 각 테스트 후 정리 (예: 트랜잭션 롤백)
  }

  @Test
  void sample() {}
}

 

 

10. @Nested

  • 역할: 컨텍스트/상태 기반 계층화
  • 적용 위치: 내부 클래스
  • 언제: 중첩(내부) 테스트 클래스를 선언해서, 시나리오/컨텍스트별로 테스트를 계층적으로 그룹화할 때 사용
  • 실무 팁: 공통 픽스처는 상위에, 상태 변경은 하위에 최소화
  • 특징/제약
    • 내부 클래스는 non-static이어야 함
    • 상위 클래스의 라이프사이클/픽스처 영향을 받음
import org.junit.jupiter.api.*;
import static org.assertj.core.api.Assertions.*;

@DisplayName("회원 서비스")
class MemberServiceTest {

  MemberService sut;

  @BeforeEach
  void setUp() {
    sut = new MemberService(/* stub/repo/mock */);
  }

  @Nested
  @DisplayName("가입 시")
  class SignUp {

    @BeforeEach
    void given_basicProfile() {
      // 공통 사전조건
    }

    @Test
    @DisplayName("정상 입력이면 가입 성공")
    void ok() {
      boolean result = sut.signUp("jay@email.com", "1234");
      assertThat(result).isTrue();
    }

    @Test
    @DisplayName("중복 이메일이면 실패")
    void duplicate_email() {
      // when
      // then
    }

    @Nested
    @DisplayName("비밀번호 정책 위반")
    class PasswordPolicy {
      @Test
      @DisplayName("너무 짧으면 실패")
      void too_short() {}
    }
  }

  @Nested
  @DisplayName("조회 시")
  class Find {
    @Test
    @DisplayName("존재하지 않으면 예외")
    void not_found() {}
  }
}

 

 

11. @ParameterizedTest (+ 소스 어노테이션들)

  • 역할: 동일 로직을 다양한 입력으로 반복
  • 적용 위치: 메서드
  • 언제: 경계값/조합 폭발/규칙 검증/대량 케이스
  • 실무 팁: CSV/파일 소스로 리뷰 친화적 테스트 데이터 관리
  • 특징/제약
    • @Test와 동시 사용 불가
    • @MethodSource 공급자 메서드는 기본 static(단, 클래스가 PER_CLASS면 인스턴스 메서드 허용)
    • @ValueSource는 null 미지원 → @NullSource/@EmptySource/@NullAndEmptySource 사용
    • @CsvFileSource는 클래스패스에서 파일 로딩, numLinesToSkip로 헤더 스킵
    • @EnumSource는 names/mode(INCLUDE|EXCLUDE|MATCH_*)로 필터 가능
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
import static org.junit.jupiter.api.Assertions.*;

class ParamTest {
  @ParameterizedTest
  @ValueSource(strings = {"SPRING","JPA"})
  void uppercase(String s){ assertTrue(s.chars().allMatch(Character::isUpperCase)); }

  @ParameterizedTest
  @NullAndEmptySource
  @ValueSource(strings = {"  "})
  void blankCases(String s){ assertTrue(s == null || s.trim().isEmpty()); }

  @ParameterizedTest
  @CsvSource({"1,3,4","2,5,7"})
  void add(int a,int b,int expected){ assertEquals(expected, a+b); }

  static java.util.stream.Stream<String> ids(){ return java.util.stream.Stream.of("U001","U002"); }

  @ParameterizedTest @MethodSource("ids")
  void idRule(String id){ assertTrue(id.startsWith("U")); }
}

 

 

12. @ExtendWith

  • 역할: Jupiter 확장(Extension) 연결(주입·훅·조건)
  • 적용 위치: 클래스
  • 언제: 공통 로깅/타임아웃/리소스/커스텀 주입
  • 실무 팁: Spring 환경에선 @SpringBootTest가 내부적으로 SpringExtension 사용
@ExtendWith(MockitoExtension.class) // ★ Mockito 확장 활성화
class PaymentServiceTest {

  @Mock OrderRepository orderRepository;   // 의존성 목
  @Mock PaymentGateway paymentGateway;     // 외부 결제 목
  @InjectMocks PaymentService sut;         // 테스트 대상(목 주입 대상)
  
  ...
  
}

 

 

 

 

13. @Tag

  • 역할: 테스트 분류/필터링
  • 적용 위치: 클래스, 메서드
  • 언제: fast/slow/db/integration/smoke 등 파이프라인 분리
  • 실무 팁: Gradle/Jenkins에서 includeTags/excludeTags로 빌드 시간 최적화
  • 특징/제약
    • 태그 이름은 간단명료(공백/특수문자 지양)
    • 중복 태그 가능, 상하위 개념 설계로 필터 전략 수립
import org.junit.jupiter.api.*;

class TagExampleTest {

  @Test
  @Tag("fast")       // 빠른 단위 테스트
  void unitFast() {}

  @Test
  @Tag("integration") // 통합 테스트
  void integrationDb() {}
}
// build.gradle (예시)
// 특정 태그만 포함/미포함하여 실행
test {
  useJUnitPlatform {
    includeTags 'fast', 'smoke'
    // excludeTags 'slow'
  }
}

 

 

마무리

위에 정리한 것보다 더 많은 어노테이션들이 존재하지만 제가 생각하기에 사용하지 않을 것 같은 어노테이션들은 제외시켰습니다. 위에 정리한 어노테이션외에 더 많은 정보를 알고 싶으시다면 Junit5 Docs

를 참고하시길 바랍니다.

 

728x90