Testcontainers
Testcontainers
DB·Redis·Kafka 같은 외부 의존이 있는 코드의 테스트는 두 갈래에서 결정합니다. 모킹으로 가짜 의존을 만들거나, 진짜 인스턴스를 띄워 검증하거나. Testcontainers 는 후자의 자리를 단순하게 만든 라이브러리입니다. Docker 위에 테스트 시점에만 임시 컨테이너를 띄우고 자동 정리합니다.
1. Testcontainers 에 대한 이야기
2015년 Sergei Egorov · Richard North 가 시작한 Java 라이브러리에서 출발했습니다. 이후 Node·Python·Go·.NET·Rust SDK 가 자라났고 PostgreSQL·MySQL·Redis·Kafka·MongoDB·Elasticsearch·LocalStack·Selenium 같은 시스템을 모듈로 다룹니다.
사이트는 testcontainers.com, 조직은 github.com/testcontainers 입니다. 라이선스는 MIT 가 다수입니다.
핵심 아이디어는 4줄로 정리됩니다.
- 테스트가 시작될 때 Docker 컨테이너를 띄웁니다.
- 무작위 포트로 노출합니다.
- 호스트(테스트 코드) 가 그 컨테이너에 접속합니다.
- 테스트가 끝나면 컨테이너를 자동 정리합니다.
2. 무엇을 해결하나
가짜로 대체한 동작이 실제 DB 의 동작과 어긋날 때 회귀를 못 잡습니다. 예를 들어 PostgreSQL 의 ON CONFLICT 동작을 mock 으로 흉내내다 실제 prod 에서 다른 결과가 나오는 사고가 흔합니다.
개발자 PC 의 DB 설정과 CI 의 DB 설정이 달라 테스트가 달라지는 자리도 있습니다. 컨테이너로 통일하면 같은 이미지 위에서 테스트가 돌아갑니다.
공유 DB 를 테스트에 쓰면 데이터가 남아 다른 테스트에 영향을 줍니다. 컨테이너가 매번 처음부터 시작하면 격리됩니다.
3. 동작 흐름
테스트 프레임워크가 Testcontainers SDK 를 호출하면 SDK 가 Docker daemon 에 컨테이너 요청을 보냅니다. 컨테이너가 내부 포트(예: PostgreSQL 의 5432) 를 호스트의 무작위 포트로 publish 합니다. SDK 가 헬스체크(예: pg_isready) 가 통과할 때까지 기다립니다. 테스트 코드가 매핑된 호스트:포트로 접속해 검증합니다. 테스트가 끝나면 SDK 가 컨테이너를 종료·정리합니다.
Ryuk
Testcontainers 가 함께 띄우는 보조 컨테이너입니다. 테스트 프로세스가 비정상 종료된 경우에도 남은 컨테이너를 정리하는 역할이며 Docker 엔진의 라벨을 보고 추적합니다.
모듈
DB 접속 문자열·포트 매핑을 매번 직접 작성하지 않도록 시스템별 모듈이 있습니다.
PostgreSQLContainer · MySQLContainer · MongoDBContainer
KafkaContainer · RedisContainer · RabbitMQContainer
LocalStackContainer
GenericContainer
각 모듈은 getJdbcUrl(), getMappedPort(...) 같은 헬퍼를 노출합니다.
4. 언어별 코드 모양
Java · JUnit
가장 잘 알려진 결합입니다. Spring Boot 와의 통합 예제가 공식 문서에 자주 등장합니다.
@Testcontainers
class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> pg =
new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("app")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
r.add("spring.datasource.url", pg::getJdbcUrl);
r.add("spring.datasource.username", pg::getUsername);
r.add("spring.datasource.password", pg::getPassword);
}
}
Node.js
import { GenericContainer } from "testcontainers";
const container = await new GenericContainer("redis:7-alpine")
.withExposedPorts(6379)
.start();
const url = `redis://${container.getHost()}:${container.getMappedPort(6379)}`;
Python · pytest
import pytest
from testcontainers.postgres import PostgresContainer
@pytest.fixture(scope="session")
def pg():
with PostgresContainer("postgres:16-alpine") as c:
yield c
세 SDK 모두 흐름은 같습니다. 띄우고, 접속하고, 정리합니다.
5. 다른 길들과의 비교
모킹 계열에는 H2·SQLite·embedded Postgres 같은 in-memory DB 가 있습니다. 빠르지만 SQL 동작 차이로 회귀를 놓치기 쉽습니다. JMock·sinon·unittest.mock 같은 mock 객체는 단위 테스트의 자리이고 통합 테스트에는 어울리지 않습니다. WireMock·MockServer·Prism 같은 HTTP 스텁은 외부 API 흉내에는 좋습니다.
진짜 인스턴스 계열에는 Docker Compose 로컬 환경이 있습니다. 사람이 미리 띄워 두고 테스트가 쓰는 자리입니다. 팀 공용 DB 는 격리가 어렵습니다. GitHub Actions 의 services: 같은 CI service container 도 있습니다.
Testcontainers 의 자리는 테스트 자체가 자기 의존을 띄우고 정리한다는 점입니다. 격리·이식성에 강점이 있습니다.
6. Reuse 모드
각 테스트마다 새 컨테이너를 띄우면 시작 시간이 길어집니다. Testcontainers 는 라벨 기반으로 컨테이너를 재사용하는 모드를 제공합니다.
TESTCONTAINERS_REUSE_ENABLE=true
로컬 개발 반복에 어울립니다. CI 에서는 보통 끕니다 (격리 우선).
7. CI 환경의 자리
GitHub Actions 의 기본 호스티드 러너에는 Docker 가 설치돼 있어 별도 설정 없이 동작하는 경우가 많습니다. Linux 러너가 자연스럽고 macOS·Windows 러너에서는 Docker 가용성이 다릅니다.
자체 호스팅 러너나 다른 CI 에서 Docker daemon 을 컨테이너 안에서 굴리는(DinD) 모양도 있습니다. Testcontainers 는 자식 컨테이너의 호스트 매핑을 다루기 위해 별도 환경 변수가 필요할 수 있습니다.
TESTCONTAINERS_HOST_OVERRIDE=...
컨테이너 풀링·기동 시간이 첫 실행에 누적됩니다. Alpine 변형은 보통 더 빠르고 이미지 캐시가 큰 도움이 됩니다.
8. 걸려 넘어지기 쉬운 곳
로컬 또는 CI 에 Docker daemon 이 없으면 테스트가 시작도 못 합니다. 프록시·VPN 환경에서 이미지 풀링이 막히는 사례도 흔하니 사내 미러 또는 사전 캐시를 둡니다.
호스트 포트를 고정하면 병렬 테스트에서 충돌합니다. 가능한 한 자동 매핑을 씁니다. 보안 정책상 라벨·소켓 접근이 제한되면 Ryuk 정리 컨테이너가 동작 못합니다. 환경 변수로 비활성도 가능하지만 잔존 컨테이너 위험이 있습니다.
같은 컨테이너를 여러 테스트가 공유하면 데이터 간섭이 일어납니다. 트랜잭션 롤백이나 스키마 격리로 막습니다. 단순 단위 테스트에 무거운 컨테이너를 띄우면 피드백이 느려지므로 단위·통합을 분리합니다.
latest 태그 의존은 회귀를 만듭니다. 마이너 버전까지 핀 고정합니다. ARM 호스트에서 amd64 전용 이미지가 느리거나 동작 안 하는 경우 multi-arch 이미지를 확인합니다. Testcontainers 의 PostgreSQL 16 과 운영의 PostgreSQL 14 가 다르면 일부 회귀를 못 잡으니 버전을 동일화합니다.
하고픈 말
Testcontainers 는 모킹과 진짜 인스턴스의 가운데 길입니다. 모킹은 빠르지만 가끔 거짓말합니다. 공유 DB 는 격리가 어렵습니다. 테스트가 자기 의존을 띄우고 정리한다는 모양이 가장 부담 없는 선택지입니다. 다음 글에서는 vitest 의 결과 실전 vitest·pytest 셋업을 정리합니다.
Next
- vitest-philosophy
- vitest-pytest-실전
LocalStack 과 MiniStack — 로컬에서 AWS 흉내내기 와 함께 보면 좋습니다. Testcontainers 공식 · PostgreSQL 모듈 · LocalStack 모듈 · Spring Boot 가이드 를 참고합니다.