어제 오늘 내일

[JUnit5] BeforeEachCallback·AfterEachCallback 확장 포인트 이해 본문

IT/JUnit

[JUnit5] BeforeEachCallback·AfterEachCallback 확장 포인트 이해

hi.anna 2026. 1. 17. 01:46

JUnit5는 테스트 실행 동작을 개발자가 자유롭게 확장할 수 있도록 Extension API를 제공합니다. 그중 가장 많이 사용되는 확장 포인트가 바로 BeforeEachCallback과 AfterEachCallback입니다.
이 두 인터페이스는 각각 각 테스트 실행 직전, 각 테스트 실행 직후에 개입할 수 있는 확장 기능을 제공합니다.

아래에서는 이 확장 포인트들의 개념, 동작 시점, 구현 패턴, 실전 예제를 순서대로 설명합니다.

 

1. BeforeEachCallback·AfterEachCallback이란?

JUnit5 확장(Extension) 기능은 테스트 라이프사이클 중 특정 시점에 사용자 코드를 개입시키는 구조입니다.

  • BeforeEachCallback
    • 각 테스트 메서드 실행 전에 호출
    • 초기화, 공통 로깅, 파라미터 바인딩 준비 등
  • AfterEachCallback
    • 각 테스트 메서드 실행 후에 호출
    • 리소스 해제, 테스트 종료 로깅 등

이는 @BeforeEach / @AfterEach 애너테이션의 코드와 비슷하지만,
테스트 클래스 바깥에서 공통 정책을 주입할 수 있다는 점이 큰 차이입니다.

즉,
테스트 내부가 아니라 “확장(Extension)” 방식으로 공통 동작을 주입할 수 있는 능력이 생깁니다.

 

2. 동작 시점 이해하기

각 콜백은 다음 순서로 실행됩니다.

BeforeAll → BeforeEachCallback → @BeforeEach → @Test → @AfterEach → AfterEachCallback → AfterAll

특징

  • 확장(Extension)의 콜백은 애너테이션 기반 메서드보다 먼저 실행됨
  • 모든 테스트 메서드마다 호출됨
  • 테스트 인스턴스가 생성된 이후에 동작

즉, 테스트 객체가 준비된 상태에서 자유롭게 테스트 실행을 가로챌 수 있습니다.

 

3. BeforeEachCallback 구현 예제

요구사항:

각 테스트 실행 직전에 테스트 메서드 이름과 시작 시간을 로깅하는 확장 기능 만들기

코드

import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class LoggingBeforeExtension implements BeforeEachCallback {

    @Override
    public void beforeEach(ExtensionContext context) {
        String testName = context.getRequiredTestMethod().getName();
        System.out.println("[BeforeEach] 테스트 시작: " + testName);
    }
}

설명

  • ExtensionContext를 활용하여 테스트 메소드·클래스·태그 등 메타데이터 접근 가능
  • 각 테스트 실행 전에 자동 호출됨

 

4. AfterEachCallback 구현 예제

요구사항:

각 테스트 실행이 끝날 때 테스트 결과(success/failure 여부)를 기록하는 확장 제작

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class LoggingAfterExtension implements AfterEachCallback {

    @Override
    public void afterEach(ExtensionContext context) {
        boolean success = context.getExecutionException().isEmpty();
        System.out.println("[AfterEach] 테스트 종료 – 성공 여부: " + success);
    }
}

설명

  • getExecutionException()이 비어 있다면 성공
  • 실패 시 예외 객체를 확인하여 디버깅 정보 제공 가능

 

5. 확장을 테스트 클래스에 적용하기

확장을 사용하는 방법은 @ExtendWith 애너테이션을 적용하는 것입니다.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith({
        LoggingBeforeExtension.class,
        LoggingAfterExtension.class
})
class ExtensionTest {

    @Test
    void sampleTest() {
        System.out.println("실제 테스트 실행");
    }
}

실행 로그 예시:

[BeforeEach] 테스트 시작: sampleTest
실제 테스트 실행
[AfterEach] 테스트 종료 – 성공 여부: true

 

6. BeforeEachCallback·AfterEachCallback 실전 활용 사례

  1. 공통 로깅 정책 적용
    • 테스트마다 로그 출력 포맷 통일
    • 테스트 문서화 자동화
  2. 리소스 자동 초기화·정리
    • DB 트랜잭션 관리
    • 임시 파일 생성/삭제
    • 테스트용 Mock 자동 초기화
  3. 테스트 메타데이터 기반 조건 처리
    • 태그(Tag)에 따라 테스트 전처리
    • 특정 테스트에서만 동작하는 커스텀 로직 삽입
  4. 실패 테스트에 대한 자동 스냅샷/로그 저장
    • 실패 시 추가 디버깅 정보 수집
  5. 테스트 실행 추적
    • CI 환경에서 테스트 실행 로그를 구조화하여 기록

 

7. 사용 시 주의할 점

  1. 테스트 코드보다 먼저 실행되므로 영향 범위를 신중히 고려
    • 전체 테스트에 적용하면 예상치 못한 부작용 발생 가능
  2. 상태 공유 문제 주의
    • Extension은 여러 테스트에서 재사용될 수 있으므로
      내부 필드를 상태 저장용으로 사용하면 테스트 간 간섭 발생 가능
  3. ExtensionContext 활용법 숙지 필수
    • 현재 테스트 메서드, 클래스, 스토어(Store) 등이 제공됨
    • 확장 로직 대부분은 이 컨텍스트를 통해 동작
  4. 테스트 성능 고려
    • 모든 테스트마다 실행되므로 무거운 작업을 넣지 않도록 주의

 

8. 정리

  • BeforeEachCallback은 테스트 실행 직전,
    AfterEachCallback은 테스트 직후에 실행되는 JUnit5 확장 포인트
  • @BeforeEach/@AfterEach보다 먼저 실행되며 테스트 외부에서 공통 동작을 주입할 때 유용
  • 확장 API는 로깅·리소스 관리·테스트 문서화·전처리 등 다양한 상황에서 활용 가능
  • ExtensionContext를 통해 테스트 메타데이터를 자유롭게 다룰 수 있음

 

반응형
Comments