자바 백엔드 입문 54편. H2 인메모리 대신 진짜 PostgreSQL을 Docker로 띄워 통합 테스트하는 Testcontainers 표준 패턴 풀어쓴 학습 노트.
이 글은 자바 백엔드 입문 시리즈 59편 중 54편이에요. 52편 @SpringBootTest 에서 H2 인메모리 DB로 테스트했죠. 이번 54편은 그 한계를 넘는 Testcontainers — 진짜 PostgreSQL·Redis·Kafka를 Docker로 띄워 테스트.
H2 인메모리의 한계
H2는 빠르고 가벼워요. 다만 "운영과 다르다" 는 결정적 단점.
- 방언 차이 — PostgreSQL의
JSONB·Array타입, MySQL의FULLTEXT INDEX같은 DB 고유 기능이 H2엔 없음 - 함수 차이 —
DATE_TRUNC·STRING_AGG같은 함수 동작 다름 - 테스트는 통과, 운영은 실패 — H2에서 OK인 SQL이 PostgreSQL에서 깨짐
해결책 = Testcontainers. "테스트할 때마다 진짜 PostgreSQL을 Docker로 띄움".
Testcontainers란
Testcontainers = "Docker 컨테이너를 자바 테스트 안에서 자동으로 띄우고 종료하는 라이브러리". PostgreSQL·MySQL·Redis·Kafka·Elasticsearch 등 거의 모든 인프라 지원.
[테스트 시작]
↓
[Testcontainers가 Docker로 PostgreSQL 띄움]
↓
[Spring Boot가 그 PostgreSQL에 연결]
↓
[테스트 실행 — 진짜 PostgreSQL 사용]
↓
[테스트 종료 — Docker 컨테이너 자동 정리]
전제 조건 = 개발 PC·CI 환경에 Docker 설치. 거의 모든 회사 환경에 있어요.
의존성 추가
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:postgresql'
testImplementation 'org.springframework.boot:spring-boot-testcontainers' // Spring Boot 3.1+
첫 테스트 — PostgreSQL
@SpringBootTest
@Testcontainers
class OrderRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void overrideProps(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private OrderRepository orderRepository;
@Test
void 주문_저장() {
Order saved = orderRepository.save(new Order(...));
assertThat(orderRepository.findById(saved.getId())).isPresent();
}
}
3단계:
1. @Container 로 PostgreSQL 컨테이너 정의
2. @DynamicPropertySource 로 Spring datasource 설정 동적 변경
3. 평범한 @SpringBootTest 테스트
테스트 시작 시 — Testcontainers가 Docker로 PostgreSQL 16 띄움. 컨테이너 준비되면 Spring이 그곳에 연결. 테스트 끝나면 컨테이너 자동 정리.
Spring Boot 3.1+ — @ServiceConnection (간편)
Spring Boot 3.1부터 더 간편한 @ServiceConnection.
@SpringBootTest
@Testcontainers
class OrderRepositoryTest {
@Container
@ServiceConnection // ← 한 줄
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@Autowired
private OrderRepository orderRepository;
@Test
void 주문_저장() { ... }
}
@ServiceConnection 한 줄이 @DynamicPropertySource 보일러플레이트 대체. PostgreSQL뿐 아니라 Redis·Kafka·MongoDB도 자동 연결.
여러 컨테이너 — Redis + PostgreSQL
@SpringBootTest
@Testcontainers
class FullIntegrationTest {
@Container @ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@Container @ServiceConnection
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
@Container @ServiceConnection
static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0"));
// 진짜 PostgreSQL + Redis + Kafka 다 띄우고 통합 테스트
}
회사 운영 환경과 거의 똑같은 상태로 테스트 가능.
컨테이너 재사용 — 빠른 테스트
매 테스트마다 컨테이너 새로 띄우면 느려요. 재사용 설정.
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
.withReuse(true); // ← 컨테이너 재사용
~/.testcontainers.properties 에 testcontainers.reuse.enable=true 박으면 — 같은 컨테이너를 여러 테스트에서 재사용. 첫 테스트만 컨테이너 시동 1~2초, 다음부터 즉시.
@DataJpaTest + Testcontainers
가벼운 JPA 슬라이스 테스트에도 적용.
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // ← H2 자동 교체 끄기
@Testcontainers
class OrderRepositoryJpaTest {
@Container @ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@Autowired private OrderRepository orderRepository;
@Autowired private TestEntityManager em;
@Test
void 상태별_조회() { ... }
}
@DataJpaTest 가 H2 자동 교체를 기본으로 하니까 — @AutoConfigureTestDatabase(replace = NONE) 로 명시적 끄기.
Spring Boot 3.1+ Devtools 모드
개발 중에도 Testcontainers 활용. main 메서드 만들기.
@TestConfiguration(proxyBeanMethods = false)
public class TestcontainersConfig {
@Bean
@ServiceConnection
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16");
}
}
// 개발 시작 시 사용
public class DevApplication {
public static void main(String[] args) {
SpringApplication.from(MyShopApplication::main)
.with(TestcontainersConfig.class)
.run(args);
}
}
DevApplication 실행 시 — 진짜 PostgreSQL 자동 시동 + Spring Boot 시작. 로컬 PostgreSQL 설치 안 해도 개발 가능.
CI 환경 — GitHub Actions
GitHub Actions에서도 Docker가 자동 제공돼서 — Testcontainers가 그대로 동작.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
- run: ./gradlew test
별도 설정 없이 PR마다 진짜 PostgreSQL로 테스트.
Docker 의존 — 개발자 PC에 Docker Desktop 필수. CI 환경에 Docker 없으면 동작 X. 컨테이너 시동에 1~10초 — 단위 테스트엔 무겁고, 통합 테스트엔 합리적. 가벼운 단위 테스트는 Mockito, 통합 테스트는 Testcontainers 조합.
한 줄 정리 — Testcontainers = 진짜 PostgreSQL·Redis·Kafka를 Docker로 자동 띄워 통합 테스트. H2 방언 차이 함정 회피. Spring Boot 3.1+ @ServiceConnection 한 줄로 표준화. 운영 환경과 똑같은 테스트.
시험 직전 한 번 더 — Testcontainers 입문자가 매번 헷갈리는 것
- Testcontainers = Docker 컨테이너를 테스트 안에서 자동 관리
- 지원 = PostgreSQL·MySQL·Redis·Kafka·Elasticsearch·MongoDB 등
- 전제 조건 = Docker 설치
- H2 인메모리의 한계 = 운영 DB와 방언·함수 차이
- 의존성 =
testcontainers/junit-jupiter+testcontainers/postgresql+spring-boot-testcontainers @Testcontainers= 클래스 레벨, 컨테이너 자동 시작·종료@Container= 컨테이너 필드 표시@DynamicPropertySource= Spring 설정 동적 변경 (옛 방식)@ServiceConnection= Spring Boot 3.1+ 간편 (권장)- 한 줄로 PostgreSQL·Redis·Kafka 자동 연결
- 컨테이너 재사용 =
.withReuse(true)+~/.testcontainers.properties - 첫 테스트만 시동, 다음부터 즉시
@DataJpaTest+ Testcontainers =replace = NONE명시- Devtools 모드 =
SpringApplication.from(...).with(...)로컬 개발 - 로컬 DB 설치 안 해도 됨
- CI 환경 (GitHub Actions) = Docker 자동 제공
- 컨테이너 시동 = 1~10초
- 가벼운 단위 테스트 = Mockito, 통합 테스트 = Testcontainers
- 두 가지 조합이 한국 회사 표준
- 운영 환경과 똑같은 테스트로 "테스트 통과 = 운영도 OK" 보장
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 49편 — JPA 메서드 이름 쿼리 @Query
- 50편 — QueryDSL 타입 안전 동적 쿼리
- 51편 — 영속성 컨텍스트와 LazyLoading
- 52편 — @SpringBootTest 통합 테스트
- 53편 — MockMvc로 컨트롤러 테스트
다음 글: