Spring

[Spring] 테스트 환경 통합

Tommy__Kim 2024. 3. 9. 18:23

들어가며 

요즘 테스트 코드의 중요성이 부각되며 테스트 코드들을 많이 작성하실 것 같습니다. 

혹시 통합 테스트코드를 작성하기 위해 다음과 같이 작성하셨다면 오늘 글이 도움이 되길 바라며 적어봅니다. 

@SpringBootTest
class MemberServiceTest {

	// test code .. 
    
}

@SpringBootTest
class PostServiceTest {

	// test code .. 
    
}

Spring TestContext Framework 

Spring TestContext Framework는 Spring Application 테스트를 위한 핵심 기능들을 제공합니다. 

  • Context Management
  • Dependency Injection of Test Fixtures
  • Transaction Management 
  • Parallel Test Execution 

이 외에도 다양한 기능들을 제공합니다. 

이 중 오늘은 Context Management과 관련하여 Context Caching에 대해서 알아보고자 합니다. 

Context Caching 

테스트 컨텍스트 프레임워크는 테스트 실행 시 ApplicationContext의 구성을 기반으로 캐시 하게 됩니다. 

그 후의 테스트에서 동일한 구성을 사용하면 ApplicationContext를 생성하지 않고, 재사용하는 방식을 채택합니다. 

이러한 방식은 테스트의 실행 시간을 줄일 수 있다는 장점이 존재합니다. 

Application Context의 생성 비용

ApplicationContext를 생성하는데 다음과 같은 작업들이 포함되기 때문에 시간이 오래 걸립니다. 

  • 설정 파일 로딩
  • Bean Definition 해석
  • 의존성 주입
  • 초기화 콜백 실행 
  • 그 외 설정
// ApplicationContext 생성 X
@Test
void nonApplicationContextTest() {
    ClassA classA = new ClassA(10);
}

// ApplicationContext 생성
@Test
void applicationContextTest() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(InnerConfig.class);
    ClassA classA = ac.getBean("classA", ClassA.class);
}

static class ClassA {
    private int number;

    public ClassA(int number) {
        this.number = number;
    }
}

@Configuration
static class InnerConfig {
    @Bean
    ClassA classA() {
        return new ClassA(10);
    }
}

 

총 두 가지의 테스트를 준비했습니다. 

  • nonApplicationContextTest
    • ApplicationContext를 별도로 생성하지 않는 테스트
  • applicationContextTest
    • ApplicationContext를 생성하는 테스트

테스트 실행 시간 비교

사진에서 확인할 수 있듯 ApplicationContext를 생성하는 테스트의 경우 상당한 시간이 소요됩니다. 

Application Context 생성 

만약 기존에 사용하던 Application Context와 다른 구성을 하게 된다면 ApplicationContext는 재생성됩니다. 

이러한 경우가 주로 발생하는 예시 중 하나가 @MockBean일 것 같습니다. 

@SpringBootTest
@Transactional
public abstract class IntegrationTestSupport {

}

class SchedulerTest extends IntegrationTestSupport {

    @MockBean
    private AlarmRepository alarmRepository;

    @MockBean
    private WebhookConnectionTester webhookConnectionTester;
 
 	// ... 
}

 

실제로 제가 작성했던 테스트코드 중 일부입니다. 

Alarm의 경우 Slack API와 연동되어 있기 때문에 실제로 빈을 등록하지 않고, @MockBean으로 처리하여 테스트코드를 작성했습니다.

 

./gradlew clean test --info를 통해 테스트를 확인해 보니 ApplicationContext가 재생성되는 것을 확인할 수 있었습니다. 

일반적으로 스프링은 @MockBean 설정이 다르거나, @MockBean이 존재하지 않는다면 다른 구성으로 판단합니다.

그렇기 때문에 새로 ApplicationContext를 생성하게 됩니다. 

환경 통합 방법

만약 특정 테스트에서 @MockBean 처리 혹은 특수 환경 설정이 필요한 것이 아니라면

테스트 환경 통합을 위한 추상클래스를 하나 만들어 둔 뒤에, 추상클래스에서 공통 처리를 해주면 환경 통합이 가능합니다. 

@SpringBootTest
@Transactional
public abstract class IntegrationTestSupport {

    @MockBean
    protected Notifier notifier;

    @MockBean
    protected AlarmRepository alarmRepository;

    @MockBean
    protected WebhookConnectionTester webhookConnectionTester;

    @MockBean
    protected MemberUpdateHandler memberUpdateHandler;
}

class SchedulerTest extends IntegrationTestSupport {

    @Autowired
    private Scheduler scheduler;
    
    // ... 
    
}

 

앞선 코드의 예시와 달리 이번에는 추상클래스에 @MockBean처리를 해 두었고, 나머지 테스트 클래스들의 경우에는 Bean과 관련해 별도의 설정을 하지 않도록 구성했습니다.

결과

환경 통합 전
환경 통합 후

  환경 통합 전 환경 통합 후 
ApplicationContext 생성 횟수 6 2
테스트 실행 시간 1sec 576ms 1sec 166ms

테스트 환경 통합 후 ApplicationContext의 생성 횟수가 줄음과 동시에 실행시간도 감축된 것을 확인할 수 있습니다.

(현재 테스트의 경우 ControllerTest는 @WebMvcTest를 사용하기 때문에 두 가지의 ApplicationContext가 생성되었습니다.)

 

참고하면 좋은 Text

- Spring.io - TestContext Framework

- Spring.io - Context Caching

'Spring' 카테고리의 다른 글

[Spring] @Retryable, Event 적용기  (1) 2023.12.04
Spring, 그리고 Test  (4) 2023.10.11
Spring MVC - 요청과 관련된 사용법 정리  (0) 2023.07.06
Junit에서 TestInstance의 생명주기  (0) 2023.07.03
NamedParameterJdbcTemplate  (0) 2023.07.01