Testcontainers로 DB 통합테스트: Docker 이미지까지 자동 띄우는 실전 가이드
로컬에서 통합테스트를 돌릴 때마다
“로컬 DB 켰나?” “테스트용 스키마는?” “동료 환경은 또 왜 깨졌지?”
이런 문제를 반복해서 겪어본 적이 있을 겁니다.
Testcontainers는 이런 고민을 줄여주기 위해,
테스트 코드 안에서 직접 Docker 컨테이너를 띄우고 내리는 방식으로
테스트마다 깨끗한 DB 환경을 자동으로 만들어 주는 라이브러리입니다.
이 글에서는 Spring Boot + JPA + PostgreSQL 조합을 기준으로,
Testcontainers로 DB 통합테스트를 설계하고 운영하는 방법을 정리합니다.

1. 왜 DB 통합테스트에 Testcontainers를 쓰는가?
전통적인 통합테스트는 보통 이렇게 진행됩니다.
1) 개발 PC나 CI 서버에 테스트용 DB를 미리 띄워두고
2) 테스트 실행 시 그 DB에 붙어서 쿼리를 검증하는 방식입니다.
문제는 환경이 늘어날수록 설정이 꼬이고,
“로컬에서는 되는데 CI에서는 깨지는” 상황을 피하기 어렵다는 점입니다.
Testcontainers는 테스트 코드 안에서 Docker 컨테이너를 직접 제어하기 때문에,
테스트가 시작될 때 자동으로 DB를 띄우고, 끝나면 알아서 정리합니다.
덕분에 다음과 같은 장점이 생깁니다.
- 모든 개발자/CI가 동일한 버전의 DB를 사용
- 테스트가 DB 스키마/마이그레이션까지 실제처럼 검증
- 필요하면 Redis, Kafka 등도 같은 방식으로 추가 가능
2. 의존성 추가 및 기본 설정
2-1) Gradle 의존성
Spring Boot 프로젝트에서 Testcontainers를 사용하려면,
테스트 스코프에 의존성을 추가합니다. 예시는 PostgreSQL 기준입니다.
// build.gradle
dependencies {
testImplementation 'org.testcontainers:junit-jupiter:1.20.1'
testImplementation 'org.testcontainers:postgresql:1.20.1'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
JUnit 5(Jupiter) 기반을 전제로 합니다.
Spring Boot 3.x를 사용한다면 기본 테스트 설정과 잘 어울립니다.
2-2) Docker 환경 전제
Testcontainers는 내부적으로 Docker를 사용하므로,
테스트를 실행하는 환경(로컬 PC, CI 서버)에 Docker가 설치되어 있어야 합니다.
CI에서는 GitHub Actions, GitLab CI, Jenkins 등에서
Docker in Docker(dind) 또는 호스트 Docker 소켓을 사용하는 구성이 일반적입니다.
3. Spring Boot + PostgreSQL 예제: 가장 기본적인 통합테스트
이제 실제로 PostgreSQL 컨테이너를 띄워서
Spring Data JPA 리포지토리를 테스트하는 예제를 작성해 보겠습니다.
@Testcontainers
@SpringBootTest
public class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass");
@Autowired
private UserRepository userRepository;
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Test
void 유저_저장_조회_테스트() {
User user = new User(null, "alice");
userRepository.save(user);
Optional<User> found = userRepository.findById(user.getId());
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("alice");
}
}
여기서 중요한 포인트는 다음과 같습니다.
- @Testcontainers: JUnit 5 확장을 통해 컨테이너 라이프사이클 관리
- @Container static 필드: 테스트 클래스 전체에서 하나의 컨테이너를 공유
- @DynamicPropertySource: 컨테이너가 띄워진 후의 JDBC URL/계정을
Spring Boot 설정으로 주입하여 실제 DB와 연결

4. 테스트 속도 최적화: 컨테이너 재사용 전략
통합테스트에서 가장 큰 고민 중 하나는 테스트 실행 속도입니다.
매 테스트마다 컨테이너를 새로 띄우면
테스트 신뢰도는 높지만 시간이 오래 걸립니다.
반대로 하나의 컨테이너를 모든 테스트에 재사용하면 빨라지지만,
테스트 간 상태 공유를 조심해야 합니다.
4-1) 클래스 단위 공유 (static 컨테이너)
앞서 본 예제처럼 static 필드에 @Container를 붙이면,
해당 테스트 클래스 내에서는 컨테이너가 한 번만 띄워집니다.
Spring Boot 통합테스트에서는 이 패턴이 가장 많이 사용됩니다.
4-2) 글로벌 재사용(reuse) 기능
Testcontainers에는 reusable containers 기능도 있습니다.
테스트 실행이 끝나도 컨테이너를 내리지 않고 재사용하는 방식인데,
로컬 개발에는 편하지만 CI에서는 컨테이너 누수가 될 수 있어
상황에 맞게 선택적으로 사용하는 것을 권장합니다.
5. 마이그레이션(Flyway/Liquibase)까지 함께 검증하기
실제 서비스에서는 DB 스키마를 수동으로 관리하지 않고
Flyway 혹은 Liquibase로 마이그레이션을 관리하는 경우가 많습니다.
Testcontainers를 사용하면,
테스트 시점에 마이그레이션이 실제로 잘 적용되는지까지 함께 검증할 수 있습니다.
spring:
datasource:
url: jdbc:tc:postgresql:16:///testdb
username: testuser
password: testpass
flyway:
enabled: true
locations: classpath:db/migration
위와 같은 jdbc:tc: 접두어를 사용하는 특별한 방식도 있지만,
Spring Boot + Testcontainers 조합에서는
앞서 본 @DynamicPropertySource 방식으로
컨테이너 → DataSource → Flyway 순서로 초기화되는 패턴을 많이 사용합니다.
이렇게 하면 CI에서도 매번 깨끗한 DB에
마이그레이션 스크립트가 그대로 적용되는지 확인할 수 있습니다.
6. 테스트 데이터 설계와 정리 전략
DB 통합테스트에서 중요한 것은
“테스트가 서로 간섭하지 않도록 상태를 관리하는 것”입니다.
대표적인 전략은 다음과 같습니다.
1) 테스트마다 트랜잭션 롤백 (Spring @Transactional + @Rollback)
2) 테스트 시작 전 truncate 스크립트 실행
3) 테스트 픽스처(Factory) 패턴으로 데이터 생성 일원화
Testcontainers 자체는 DB를 띄우고 내리는 역할에 집중하고,
데이터 정리는 기존의 JPA/Spring 테스트 패턴과 조합해서 사용하는 것이 좋습니다.

7. 운영 관점에서의 Testcontainers 활용 포인트
Testcontainers는 단순히 “테스트에서 Docker 컨테이너를 띄운다”를 넘어서,
운영 환경과 최대한 비슷한 구성을 그대로 가져와 검증할 수 있다는 장점이 있습니다.
예를 들어,
- 운영과 동일한 버전의 PostgreSQL 이미지 사용
- 로컬에서도 실제와 같은 Collation, Extension 설정 확인
- 장애 재현 시, 동일 버전의 DB/Redis/Kafka를 테스트 코드로 재현
이렇게 통합테스트 레벨에서 “운영과의 거리”를 줄여놓으면,
실제 배포 후에 겪게 될 버그를 상당 부분 사전에 걸러낼 수 있습니다.
8. 정리 및 체크리스트
- 테스트 환경에 Docker가 안정적으로 동작하는가?
- Testcontainers 의존성을 프로젝트에 추가했는가?
- @Testcontainers + @Container + @DynamicPropertySource 패턴을 이해했는가?
- 마이그레이션(Flyway/Liquibase)을 통합테스트에서 함께 검증하는가?
- 테스트 데이터가 서로 간섭하지 않도록 정리 전략을 세웠는가?
Testcontainers를 한 번 익혀두면,
새로운 서비스가 생길 때마다 “테스트용 DB 구축” 고민을 크게 줄일 수 있습니다.
이 글을 바탕으로 프로젝트에 안정적인 DB 통합테스트를 도입해 보시길 바랍니다.
👉 1편: Ubuntu 24.04에서 Nginx로 무료 SSL(HTTPS) 적용
👉 5편: Gradle 빌드 최적화로 빌드 50% 줄이기
👉 6편: Spring Security 6 JWT 로그인/리프레시 토큰
👉 8편: Docker로 PostgreSQL 운영(백업/복구/업그레이드)
'Database & Storage' 카테고리의 다른 글
| SQL 쿼리 정렬과 주석 달기 실전 가이드 — 초보 개발자도 읽히는 쿼리 쓰는 법 (1) | 2025.11.07 |
|---|---|
| Loki + Promtail + Grafana로 로그 수집·검색·알림: 가벼운 ELK 대안 구축(단일 서버 → 다중 서버 확장) (0) | 2025.10.28 |
| 다운타임 없는 DB 마이그레이션 완전 가이드: PostgreSQL & MySQL(Online DDL, gh-ost/pt-osc, Expand→Migrate→Contract) (0) | 2025.10.28 |
| Micrometer·Prometheus·Grafana로 스프링 부트 서비스 모니터링 대시보드 만들기 (0) | 2025.10.10 |
| Docker로 PostgreSQL 운영하기: 백업·복구·업그레이드 실전 가이드 (0) | 2025.10.09 |