| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- 자바
- junit
- java테스트
- 단위테스트
- Java
- 자바스크립트
- 인텔리제이
- js
- vscode
- 자바문법
- math
- string
- SpringBoot
- 배열
- Array
- IntelliJ
- 테스트자동화
- javascript
- input
- list
- CSS
- junit5
- 정규식
- HashMap
- 스프링부트
- Visual Studio Code
- Eclipse
- ArrayList
- html
- 문자열
- Today
- Total
어제 오늘 내일
[Spring Boot] "H2랑 MySQL 문법이 달라서 터졌어요..." TestContainers로 완벽한 통합 테스트 환경 구축하기 본문
[Spring Boot] "H2랑 MySQL 문법이 달라서 터졌어요..." TestContainers로 완벽한 통합 테스트 환경 구축하기
hi.anna 2026. 3. 24. 09:58
우리는 보통 테스트할 때 빠르고 간편한 H2 데이터베이스(In-memory)를 사용합니다. 하지만 운영 환경은 MySQL이나 PostgreSQL을 쓰죠. 여기서 치명적인 문제가 발생합니다.
- MySQL:
INSERT IGNORE...(지원함) - H2: "그게 뭐죠? 에러 뿜!" (지원 안 함, 혹은 문법 다름)
결국 "테스트는 통과했지만 배포하면 망하는" 시한폭탄을 안고 가는 셈입니다.
이 문제를 해결하기 위해 등장한 것이 바로 Testcontainers입니다.
1. Testcontainers가 뭔가요?
자바 코드(JUnit)에서 Docker 컨테이너를 직접 제어하는 라이브러리입니다.
- 테스트가 시작되면 (
@BeforeAll) - 도커로 진짜 MySQL 컨테이너를 띄웁니다.
- 테스트를 수행합니다.
- 테스트가 끝나면 (
@AfterAll) 컨테이너를 삭제합니다.
즉, 내 컴퓨터에 MySQL을 깔지 않아도, 테스트 순간만큼은 완벽한 운영 환경을 복제해 내는 것이죠.
2. 준비물: Docker Desktop
이 기술은 도커를 사용하므로, 개발 PC에 Docker Desktop이 켜져 있어야 합니다. (이게 유일한 단점입니다.)
3. 스프링 부트 3.1+ 설정 (의존성 추가)
스프링 부트 3.1부터는 Testcontainers 지원이 엄청나게 강력해졌습니다. build.gradle에 다음을 추가하세요.
dependencies {
// 1. 기본 라이브러리
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
// 2. 주니터 5와 연동
testImplementation 'org.testcontainers:junit-jupiter'
// 3. 내가 쓸 DB 컨테이너 (MySQL)
testImplementation 'org.testcontainers:mysql'
}
4. 실전 코드: "@ServiceConnection"의 마법
예전에는 IP랑 포트를 찾아서 설정 파일에 넣어주는 복잡한 코드가 필요했습니다 (@DynamicPropertySource).
하지만 이제는 @ServiceConnection 어노테이션 하나면 스프링이 알아서 다 연결해 줍니다.
통합 테스트 클래스 만들기 ()
@DataJpaTest // JPA 관련 빈만 로드
@AutoConfigureTestDatabase(replace = Replace.NONE) // H2로 바꿔치기 하지 마! (내꺼 쓸 거야)
@Testcontainers // 1. 이 클래스는 컨테이너를 씁니다.
class MemberRepositoryTest {
// 2. 도커로 MySQL 8.0 버전을 띄워라!
@Container
@ServiceConnection // ★ 핵심: 스프링아, 이 컨테이너 정보를 네 DataSource로 써!
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");
@Autowired
MemberRepository memberRepository;
@Test
void 회원저장_테스트() {
// given
Member member = new Member("testUser");
// when
Member savedMember = memberRepository.save(member);
// then
assertThat(savedMember.getId()).isNotNull();
// 진짜 MySQL에서 저장되고 조회된 것임!
}
}
실행 결과:
테스트를 돌려보면 콘솔창에 도커 로그가 찍히면서 MySQL 컨테이너가 뿅 하고 떴다가, 테스트가 끝나면 사라집니다.
5. Redis도 띄워볼까요?
DB뿐만 아니라 Redis도 똑같습니다. 로컬에 Redis 깔 필요 없습니다.
@SpringBootTest
@Testcontainers
class RedisTest {
@Container
@ServiceConnection
static GenericContainer<?> redis = new GenericContainer<>("redis:alpine")
.withExposedPorts(6379); // 6379 포트 열어줘
@Autowired
StringRedisTemplate redisTemplate;
@Test
void 레디스_테스트() {
redisTemplate.opsForValue().set("key", "value");
String value = redisTemplate.opsForValue().get("key");
assertThat(value).isEqualTo("value");
}
}
6. 단점은 없나요? (속도 이슈)
세상에 공짜는 없습니다. Testcontainers는 느립니다.
매번 테스트할 때마다 도커 이미지를 다운받고(처음 한 번만), 컨테이너를 띄우는 데 몇 초가 걸립니다.
- 해결책: 모든 테스트마다 띄우지 말고, 추상 클래스(AbstractIntegrationTest)를 하나 만들어서 컨테이너를 static으로 선언하세요. 그러면 딱 한 번만 띄우고 모든 테스트가 그 컨테이너를 재사용합니다. (싱글톤 패턴)
// 모든 통합 테스트의 부모 클래스
@Testcontainers
public abstract class IntegrationTestSupport {
@Container
@ServiceConnection
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");
}
마치며
오늘의 결론입니다.
- Testcontainers를 쓰면 H2의 한계(문법 차이)를 극복할 수 있다.
@ServiceConnection덕분에 설정이 정말 간편해졌다. (Spring Boot 3.1+)- Docker만 깔려있다면, 팀원 누구나 똑같은 환경에서 테스트를 돌릴 수 있다.
이제 "제 로컬에선 되는데요?"라는 변명은 통하지 않습니다. 완벽한 격리 환경에서 자신 있게 배포하세요!
다음 포스팅에서는 "이메일 형식 검사, if문으로 짜다가 밤새실 건가요?" 입력값 검증의 정석 @Valid와 커스텀 검증 어노테이션 만들기에 대해 알아보겠습니다.
도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊
