본문 바로가기
스프링부트

스프링부트 테스트(1) - 동작원리와 어노테이션 친해지기

by pius712 2024. 7. 23.

동작 원리

TestContext 를 bootstrap 하는 원리

테스트 컨텍스트란, 테스트시 application context 를 말하며 애플리케이션의 구성 정보를 구성하는 것을 말한다. 예를들어,애플리케이션의 Bean 을 로드하는 것도 이에 포함된다고 할 수 있다.

그렇다면, 이 테스트 컨텍스트는 어떻게 로드 될까? 이를 알기 위해서 @SpringBootTest 어노테이션을 확인해보자.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// TestContextBootstrapper 구현체 : SpringBootTestContextBootstrapper
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
public @interface SpringBootTest 

테스트 프레임워크(Junit)의 ExtendWith 은 테스트의 기능을 확장하기 위한 것인데, SpringBootTest 에서는 SpringExtension 을 사용하고 있다.

SpringExtension 코드를 따라가보면, @BootStrapWith 어노테이션에 명시된 TestContextBootStraper 를 이용하여 테스트 컨텍스트를 구성한다. 즉, 스프링에서 통합 테스트(@SpringBootTest) 를 할 때, TestContextBootstrapper 인터페이스 구현체가 테스트 컨텍스트를 로드한다.

무엇으로 context 를 구성하는가?

해당 부분은 2편에서 추가적으로 다루고자 한다.

https://pius712.tistory.com/36

 

테스트에 사용되는 어노테이션과 친해지기

테스트 기능 확장하기 - @ExtendWith

SpringBootTest 어노테이션에는 @ExtendWith(SpringExtension.class) 어노테이션이 붙어있는데, 이건 무엇을 뜻할까?

참고로 JUnit4 에서는 @RunWith(SpringRunner.class) 어노테이션이 사용되었으나,

JUnit5 에서는 @ExtendWith(SpringExtension.class) 어노테이션으로 대체되었다.

RunWith 과 Extension 자체가 동일한 것은 아니지만, SpringRunner 의 동작을 Extension 을 구현하는 방식으로 변경된 것 같다.

ExtendWith 을 통해서 테스트 관련 확장 기능을 제공할 수 있다.

JUnit 5 확장 모델은 다음과 같은 다양한 인터페이스를 통해 확장을 구현할 수 있음

  • BeforeAllCallback / AfterAllCallback
  • BeforeEachCallback / AfterEachCallback
  • TestExecutionExceptionHandler
  • ParameterResolver
  • 등등

이러한 인터페이스를 구현하여, 테스트 실행 전후에 특정 작업을 수행하거나, 테스트 메소드에 파라미터를 주입하는 등의 기능을 추가할 수 있다.

예를들어, 각 테스트의 수행 시간을 체크하는 기능을 아래처럼 만들 수 있다.

// 테스트 extension 생성
class LoggingExtension : TestWatcher {

    private val log: Logger = LoggerFactory.getLogger(javaClass)
    private var startTime: Long = 0

    override fun testSuccessful(context: ExtensionContext) {
        val duration = System.currentTimeMillis() - startTime
        log.info("Test '${context.displayName}' passed after $duration ms")
    }

    override fun testFailed(context: ExtensionContext, cause: Throwable) {
        val duration = System.currentTimeMillis() - startTime
        log.info ( "Test '${context.displayName}' failed after $duration ms" )
    }
}

// 적용하는 법
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@ActiveProfiles("local")
@ExtendWith(LoggingExtension::class) // 적용
abstract class IntegrationTest

Profile 변경하기 - @ActiveProfiles

기본적으로 테스트 수행시 아무런 설정을 하지 않으면 profile 이 “default” 값으로 테스트가 실행된다.

profile 을 변경하기 위해서는 @ActiveProfiles 를 사용하여, 변경할 수 있다.

profile 이 변경되는 경우, 설정 정보도 profile 에 맞게 변경되어 context 가 생성된다.

@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@ActiveProfiles("local") // dev, prod 등으로 변경할 수 있다.
class IntegrationTest 

구성정보 바꾸기 - @Configuration , @TestConfiguration

해당 부분도 2편에서 더 자세하게 다루려고 한다.

https://pius712.tistory.com/36

 

환경변수 바꾸기 - @TestPropertySource

application properties를 테스트 전용 값으로 바꿔칠 수 있다.

@TestPropertySource(properties = ["datasource.connection-pool=3"])
class TestMockTest(
    private val testService: TestService,
):IntegrationTest() {
    @Test
    fun test() {
        testService.test()
    }
}