Contents
see List개요
Testcontainers는 테스트 실행 시 Docker 컨테이너를 자동으로 시작하고 종료하여, 실제 인프라에 가까운 환경에서 통합 테스트를 수행할 수 있게 하는 라이브러리입니다. H2 같은 인메모리 DB 대신 실제 PostgreSQL을, MockServer 대신 실제 Redis나 Kafka를 사용하여 프로덕션과 동일한 조건에서 테스트합니다. Spring Boot 3.1부터 공식 지원이 시작되어, 설정이 대폭 간소화되었습니다.
핵심 개념
Container Lifecycle은 테스트 라이프사이클과 연동됩니다. 테스트 클래스가 시작될 때 컨테이너가 생성되고, 모든 테스트가 끝나면 자동으로 정리됩니다. @Testcontainers 어노테이션과 @Container 어노테이션으로 선언적 관리가 가능합니다.
Dynamic Properties는 컨테이너가 할당받은 랜덤 포트 등의 동적 정보를 Spring 프로퍼티로 주입하는 메커니즘입니다. @DynamicPropertySource나 Spring Boot 3.1+의 @ServiceConnection을 사용합니다.
Reusable Containers는 테스트 실행 간에 컨테이너를 재사용하여 테스트 속도를 높이는 기능입니다. 로컬 개발 환경에서 특히 유용합니다.
실전 예제
Spring Boot 3.1+ 스타일의 Testcontainers 설정입니다.
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
@ServiceConnection을 사용한 간편 설정 (Spring Boot 3.1+):
@SpringBootTest
@Testcontainers
class OrderRepositoryTest {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@Container
@ServiceConnection
static GenericContainer<?> redis =
new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
@Autowired
private OrderRepository orderRepository;
@Test
void shouldSaveAndFindOrder() {
Order order = Order.builder()
.customerId("CUST-001")
.totalAmount(BigDecimal.valueOf(50000))
.status(OrderStatus.CREATED)
.build();
Order saved = orderRepository.save(order);
Optional<Order> found = orderRepository.findById(saved.getId());
assertThat(found).isPresent();
assertThat(found.get().getCustomerId()).isEqualTo("CUST-001");
assertThat(found.get().getTotalAmount())
.isEqualByComparingTo(BigDecimal.valueOf(50000));
}
}
공통 컨테이너 설정을 추상 클래스로 분리합니다.
@Testcontainers
public abstract class IntegrationTestBase {
@Container
@ServiceConnection
protected static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb")
.withInitScript("schema.sql");
@Container
@ServiceConnection
protected static final KafkaContainer kafka =
new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.5.0")
);
@Container
@ServiceConnection
protected static final GenericContainer<?> redis =
new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
}
// 실제 테스트 클래스
@SpringBootTest
class OrderIntegrationTest extends IntegrationTestBase {
@Autowired private OrderService orderService;
@Autowired private KafkaTemplate<String, OrderEvent> kafkaTemplate;
@Test
void shouldProcessOrderAndPublishEvent() {
OrderRequest request = new OrderRequest("CUST-001",
List.of(new ItemRequest("PROD-001", 2)));
Order order = orderService.createOrder(request);
assertThat(order.getStatus()).isEqualTo(OrderStatus.CREATED);
// Kafka 메시지 발행 확인
// ...
}
}
개발 환경에서 Testcontainers를 DevService로 활용합니다.
// src/test/java 하위에 TestApplication 정의
@TestConfiguration(proxyBeanMethods = false)
public class TestContainerConfig {
@Bean
@ServiceConnection
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16-alpine")
.withReuse(true); // 컨테이너 재사용
}
@Bean
@ServiceConnection
GenericContainer<?> redisContainer() {
return new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379)
.withReuse(true);
}
}
// 테스트용 main 메서드 - ./gradlew bootTestRun
public class TestApplication {
public static void main(String[] args) {
SpringApplication.from(Application::main)
.with(TestContainerConfig.class)
.run(args);
}
}
활용 팁
- 컨테이너 재사용:
~/.testcontainers.properties에testcontainers.reuse.enable=true를 설정하면 로컬에서 컨테이너를 재사용하여 테스트 속도를 크게 향상시킵니다. - CI 환경: GitHub Actions, GitLab CI 등에서는 Docker-in-Docker 또는 호스트 Docker 소켓 마운트가 필요합니다.
- 초기 데이터:
withInitScript()로 스키마와 초기 데이터를 설정하거나, Flyway/Liquibase 마이그레이션을 자동 실행하세요. - 병렬 테스트: 각 테스트 클래스가 독립된 컨테이너를 사용하면 병렬 실행 시 격리가 보장됩니다.
- Wait Strategy: 컨테이너 준비 상태를 정확히 감지하기 위해
withStartupTimeout()과 커스텀 WaitStrategy를 설정하세요.
마무리
Testcontainers는 "테스트 환경은 프로덕션과 같아야 한다"는 원칙을 실현합니다. H2와 목 객체에 의존하던 시대를 지나, 이제는 실제 PostgreSQL, Redis, Kafka를 테스트에서 그대로 사용할 수 있습니다. Spring Boot 3.1+의 @ServiceConnection 덕분에 설정도 매우 간단합니다. 통합 테스트의 신뢰성을 높이고 싶다면, Testcontainers 도입을 강력히 추천합니다.