공부/BE, DB

TestContainers를 이용한 테스트 환경 구성

동형2 2022. 3. 1. 17:25

Test 환경 구성 방법

테스트 환경을 구성하기 위해서는 다양한 방법이 존재한다.
대표적으로 Spring Boot는 Data Access 관련 테스트를 위해 H2 database(in-memory db)를 자동으로 Configuration 해준다.

많이 쓰이는 다른 방법으로는 바로 Docker를 이용하는 방법이 있는데, 컨테이너들을 띄워 테스트를 하기 위해 필요한 의존관계(대표적으로 데이터베이스, 다른 dependent 서비스들 포함)을 편리하게 설정해줄 수 있다.

또다른 방법으로는 글에서 소개할 TestContainers 를 이용하는 방법이 있다.

TestContainers란 ?

도커를 이용하여 테스트 환경을 구성해줄 수 있는 라이브러리다. 기존에 도커를 이용해 테스트 환경을 구성한다 하면, 보통 docker-compose을 이용해 구성할 수 있는데 이는 테스트 환경 구성을 스프링 외부에서 해야한다는 단점이 존재한다.

반면 TestContainers 를 이용한다면 코드 레벨에서의 컨테이너 설정, inter-container 간의 네트워크 설정 등을 할 수 있다.

 

TestContainers의 장점

위에서 언급한 코드 레벨에서의 컨테이너 설정 외에도 다양한 장점이 존재한다.

대표적으로 로그 관련으로, 컨테이너의 로그를 Spring Process로 파이프할 수 있다.

즉 테스트 환경에서 Applciation Log와 Container Log를 함께 보며 디버깅 할 수 있다는 장점이 있다.

 

또한 Docker에 의존성이 존재하지만, 로컬에 있는 도커에 연결(docker.sock) 뿐만 아니라 tcp를 통해서 외부에 있는 도커 서버와도 연결해 사용할 수 있다.

그리고 구체적인 Container 클래스를 제공한다. 대표적으로는 JdbcDatabaseContainer로 객체가 jdbcUrl 등의 멤버들을 제공한다.

 

Configuration : 1. dependency 설정

예제 환경은 Spring Boot(2.6.2), Kotlin(1.6.10), Gradle Kotlin DSL을 이용해 진행하였다.

다음과 같은 디펜던시가 필요하다. build.gradle.kts에 다음 의존성을 추가한다. 여기서는 MySQL을 이용했다.

 

testImplementation("org.testcontainers:junit-jupiter:1.16.3")
testImplementation("org.testcontainers:mysql:1.16.3")

 

Configuration : 2. Container 설정

생성할 컨테이너와 관련된 설정이다. Spring과 의존성을 위해 @TestConfiguration 을 이용하였다.

 

@TestConfiguration("TestMySQLContainer")
class TestMySQLContainer {
        companion object {
          @Container
          @JvmStatic
          val container = MySQLContainer<Nothing>("mysql:8.0.19")
              .apply {
                  withDatabaseName("test")
                  withUsername("root")
                  withPassword("root")
              }
              .apply {
                  start()
              }
    }
}

 

Configuration : 3. Datasource 빈 작성하기

해당 컨테이너와 연결될 데이터소스를 직접 정의한다.

 

@TestConfiguration
class DataSourceConfig {
    @Bean
    @DependsOn("TestMySQLContainer")
    fun dataSource(): HikariDataSource {
        return DataSourceBuilder.create()
            .type(HikariDataSource::class.java)
            .url(TestMySQLContainer.container.jdbcUrl)
            .username(TestMySQLContainer.container.username)
            .password(TestMySQLContainer.container.password)
            .build()
    }
}

 

컨테이너가 생성된 이후 빈이 생성되도록 DependsOn 어노테이션을 이용하였다.
또한 MySQLContainer 타입으로 컨테이너를 생성했기 때문에 객체가 jdbcUrl, username 등 데이터소스 생성에 필요한 변수들을 제공해준다.

특히 jdbcUrl 을 통해서 포트, dbname 등에 상관없이 데이터소스를 생성할 수 있다.

 

DataJpaTest 에서의 테스트

DataJpaTest를 이용하면 테스트에 필요한 빈들만 띄워서 테스트할 수 있다.

 

@DataJpaTest
class FooTest {}

 

DataJpaTesth2 데이터베이스를 테스트에 이용하는데, 먼저 이거를 disable 시켜줘야 한다.

 

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)

 

또한 DataJpaTest는 컴포넌트스캔을 하지 않으므로, 명시적으로 필요한 빈들을 Import 해야 한다. 위에서 생성한 두개의 클래스를 Import한다.

 

@DataJpaTest
@Import(TestMySQLContainer::class, DataSourceConfig::class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)

 

이제 테스트를 실행한다면 기존의 AutoConfiguration 대신 직접 생성한 DataSource가 적용되어 테스트가 실행된다.

Streaming Log(Container -> Spring Process)

마지막으로 MySQL 컨테이너 로그를 스프링 테스트 환경에서 볼 수 있도록 로그를 파이프 해줄 수 있다.

먼저 앞에서 생성한 TestMySQLContainer 클래스를 다음과 같이 수정한다.

 

@TestConfiguration("TestMySQLContainer")
class TestMySQLContainer {
        companion object {
          val logger = LoggerFactory.getLogger(TestMySQLContainer::class.java)
          @Container
          @JvmStatic
          val container = MySQLContainer<Nothing>("mysql:8.0.19")
              .apply {
                  withDatabaseName("test")
                  withUsername("root")
                  withPassword("root")
              }
              .apply {
                  start()
              }
              .apply {
                followOutput(Slf4jLogConsumer(logger))
              }
    }

 

 SLF4J를 이용해 로그 인스턴스를 만들어 파이프해준다.

 

References

TestContainers

TestContainer로 멱등성 있는 테스트..