테스트 코드를 작성하면서 테스트 환경을 구축하는 것에 어려움을 겪었다. 특히 테스트를 할 때는 보통 내장 DB인 H2를 많이 사용하게 되는데, H2를 사용하면 테스트 환경과 운영 환경 사이에 간극이 생기는 경우가 있었다. 이를 해결할 수 있는 방법이 없을까 찾아보다가 Testcontainers라는 것을 알게 되었고, 덕분에 복잡한 설정 없이 테스트 환경을 구축할 수 있었다. Testcontainers에 대해 기억하기 위해 블로그를 작성하고자 한다!
도커 컨테이너에서 실행할 수 있는 테스트 라이브러리이다. 도커 컨테이너에서 실행이 되기 때문에 반드시 도커가 설치되어 있어야 하며, 모든 상황에서 일관된 테스트 환경을 보장한다.
또한, 도커 컨테이너를 활용해서 외부 의존성을 포함한 테스트 환경 구축 및 관리를 편리하게 할 수 있다는 장점이 있다. 테스트가 시작되면 자동으로 컨테이너가 실행되고, 테스트가 종료되면 자동으로 컨테이너가 정리된다.
Testcontainers를 사용하기 전에 알아두어야 할 몇 가지 주의사항이 있다.
spring:
datasource:
url: jdbc:h2:mem:testdb
username: username
password: password
driver-class-name: org.h2.Driver
H2를 사용하면 간편하게 내장 DB를 통해 테스트를 할 수 있지만 문제점이 있다.
MySQL 혹은 PostgreSQL 등의 전용 문법이 H2에서 동작하지 않는 경우가 있다. 반대로 H2에서는 동작하지만, 타DB에서 지원하지 않는 문법도 있다. 따라서 테스트는 통과했으나 운영 환경에서 오류가 발생할 수 있다.
spring:
datasource:
url: jdbc:tc:mysql:8.0:///testdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: username
password: password
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
하지만 Testcontainers를 사용하면 이와 같은 문제를 해결할 수 있다. yml 파일을 통해 Testcontainers의 DB를 설정하는 코드이다. 이렇게 하면 MySQL 컨테이너에서 테스트가 가능하다. 물론, 설정을 통해 MySQL 외에도 다양한 DB를 사용할 수 있다. 개발자 환경에 DB를 따로 설치하지 않아도 되며 컨테이너를 통해 알아서 처리된다.
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:testcontainers'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:mysql'
testImplementation 'org.testcontainers:jdbc'
}
testImplementation을 사용하여 테스트 범위에만 의존성을 추가한다.
| 의존성 | 설명 |
|---|---|
spring-boot-starter-test | Spring Boot 테스트 라이브러리 JUnit, AssertJ, Mockito 등 테스트를 위한 기본 라이브러리를 포함한다 |
testcontainers | Testcontainers 코어 라이브러리 |
junit-jupiter | @Testcontainers, @Container 등의 어노테이션을 제공한다 |
mysql | MySQL 전용 컨테이너 모듈 사용하는 DB에 따라 다른 라이브러리 사용할 수 있다 (예: postgresql, mongodb) |
jdbc | JDBC URL을 통한 간편한 컨테이너 실행을 지원한다 |
@TestConfiguration
@Testcontainers
public class TestContainerConfig {
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("testdb")
.withUsername("username")
.withPassword("password")
.withReuse(true); // 컨테이너 재사용 활성화
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
registry.add("spring.datasource.username", mysql::getUsername);
registry.add("spring.datasource.password", mysql::getPassword);
registry.add("spring.datasource.driver-class-name", () -> "com.mysql.cj.jdbc.Driver");
}
@Bean
public MySQLContainer<?> mysqlContainer() {
return mysql;
}
}
Testcontainers 설정을 위한 Config 파일을 작성한다.
Config 파일을 작성해도 되고, 위처럼 yml 파일을 통해 Testcontainers 설정을 할 수도 있다.
| 어노테이션 | 상세 설명 |
|---|---|
@TestConfiguration | 테스트 설정 클래스 지정 테스트 실행 시에만 로드되도록 한다 |
@Testcontainers | Testcontainers의 생명주기를 관리한다 |
@Container | 관리해야 하는 컨테이너임을 표시한다 static으로 선언하면 클래스 레벨에서 컨테이너를 공유할 수 있어, 여러 테스트 메서드에서 동일한 컨테이너를 사용할 수 있다 이렇게 하지 않으면 테스트 메서드가 실행될 때마다 새로운 컨테이너가 생성되어 테스트 속도가 느려질 수 있다 |
@DynamicPropertySource | 컨테이너 실행 이후에 동적으로 생성되는 정보를 환경변수로 주입한다 주입된 설정으로 실제 DB에 연결하여 테스트를 수행한다 |
@Testcontainers가 @Container 필드를 찾아 MySQL 컨테이너를 시작한다.@DynamicPropertySource가 동적 정보를 Spring에 주입한다.@SpringBootTest
@ContextConfiguration(classes = TestContainerConfig.class)
class HealthControllerTest {
@Test
void healthCheck() {
ExtractableResponse<Response> response = RestAssured.given().log().all()
.when()
.get("/health/check")
.then().log().all()
.extract();
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
}
}
@ContextConfiguration을 통해 앞서 작성한 Testcontainers 설정을 주입한다. 이렇게 하면 MySQL 컨테이너에 연결되어 테스트가 실행된다. H2가 아닌 실제 MySQL DB에서 테스트를 진행하기 때문에 운영 환경과 동일한 조건에서 검증이 가능하다.
Testcontainers는 기본적으로 테스트를 진행할 때마다 새 컨테이너를 생성한다. 테스트를 실행할 때마다 컨테이너 생성/삭제로 인한 오버헤드가 발생하는데, 재사용 옵션을 통해 이를 개선할 수 있다.
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("testdb")
.withUsername("username")
.withPassword("password")
.withReuse(true);
Config 파일에서 withReuse() 옵션을 통해 컨테이너 재사용 옵션을 추가할 수 있다. withReuse(true)로 설정하면 첫 실행에는 컨테이너를 생성하고, 이후 실행에서는 이미 만들어진 기존 컨테이너를 재사용한다.
testcontainers:
reuse:
enable: true
Config 파일과 마찬가지로 yml 파일에서도 컨테이너 재사용 옵션을 추가할 수 있다.
Testcontainers를 통해 yml 혹은 Config 파일을 정의하여 편리하게 테스트 환경을 구축할 수 있었다. Testcontainers를 사용하면 실제 운영 환경과 동일한 DB에서 테스트를 할 수 있어, 운영 환경에서 발생할 수 있는 문제를 사전에 발견할 수 있다. 이번에는 단일 모듈에서 테스트 코드를 작성하여 테스트 의존성을 따로 관리하지 않아도 괜찮았지만 멀티 모듈 환경에서는 TestFixtures를 통해 하위 모듈에 의존성을 전파해주어야 한다. 이 방식도 정리하고자 한다!
Testcontainers
The simplest way to replace H2 with a real database for testing
MySQL Module - Testcontainers for Java
Testcontainers로 통합테스트 만들기
TestContainers로 유저시나리오와 비슷한 통합테스트 만들어 보기
Spring Boot에서 Testcontainers로 통합 테스트 환경 구축하기