GitHub - pbg0205/concert
Contribute to pbg0205/concert development by creating an account on GitHub.
github.com
현재 사이드 프로젝트에서 로컬 환경에서 main 과 test 의 DB 환경을 독립적으로 구성하기 위해 test container 를 적용했다. Test Container 를 적용하며 경험했던 삽질 기록을 기록해보고자 한다.
[1] test container 를 사용하는 이유
- 실제 서비스와 동일한 환경 : Docker 를 사용해 DB, MQ, API 등 외부 시스템을 실제 운영 환경와 유사한 환경에서 테스트할 수 있는 장점이 있다.
- 테스트 독립성 보장 : 각 테스트를 독립된 컨테이너에서 실행되므로 테스트 고립성을 보장할 수 있다.
[2] 테스트 컨테이너 구성 방법 (with.gradle)
(1) build.gradle
ext {
set('testcontainers.version', "1.19.8")
}
dependencies {
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:mysql")
}
(2) 테스트 컨테이너 생성 방법
테스트 컨테이너를 사용하기 위해서는 @TestContainers, @Container 가 필요하다.
- @TestContainers
- 해당 테스트 클래스가 TestContainers 를 사용할 것을 선언하는 클래스 레벨 어노테이션(ElementType.TYPE)이다.
- 해당 어노테이션이 분은 클래스에서 선언된 @Container 필드의 생명 주기를 관리하며, 테스트 시작 시 컨네이너를 자동으로 시작하고, 테스트 종료 시 정리를 수행한다.
- TestContainer 를 사용하는 모든 테스트 클래스에 반드시 선언해야 한다.
- @Container
- 테스트에서 사용할 컨테이너를 정의하는 어노테이션이다.
- 해당 필드가 TestcContainers 가 관리하는 컨테이너임을 나타내며 멤버 변수 레벨(ElementType.FIELD) 이다.
- @Container 로 기본적으로 선언된 컨테이너틑 테스트 시작 시 자동 생성, 종료 시 자동 정리된다.
- 컨테이너를 static 으로 선언하면, 테스트 클래스의 모든 메서드가 같은 컨테이너를 공유한다.
@Testcontainers
public abstract class RestDocsDocumentationTest {
@Container
static MySQLContainer<?> MYSQL_CONTAINER = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
.withDatabaseName("concert_db")
.withUsername("root")
.withPassword("concert2025");
@BeforeAll
static void beforeAll() {
MYSQL_CONTAINER.start();
}
@AfterAll
static void afterAll() {
MYSQL_CONTAINER.stop();
}
}
(3) application.yml 설정
테스트 컨테이너 전용 spring.datasource.url 과 spring.datasource.driver-class-name 을 설정해야 한다.
- spring.datasource.url : jdbc:tc:[image:version]://[db-name]
- spring.datasource.driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
spring:
datasource:
url: jdbc:tc:mysql:8://concert_db
username: root
password: concert2025
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
[3] test container 삽질 기록
(1) 테스트 컨테이너 재사용할 때 @Configuration 을 고려해보자.
class 단위에서 @TestContainers 를 사용하면 클래스 단위의 컨테이너 라이프 사이클 관리만 가능하다. 즉, 테스트 클래스 실행, 종료 단위에서만 라이프 사이클이 가능하다.
하지만, @Configuration 을 통한 @PreDestroy 를 사용하면 테스트 컨테이너를 종료하는 시점에 컨테이너를 종료해 실행, 종료를 최소화하여 테스트 실행 시간을 줄일 수 있다.
또한, @Configuration 을 선언하면 통합 테스트를 동작시키기 위한 설정으로 등록되고 static 키워드를 통해 TestContainer 를 재사용하고 있어 TestContext Caching으로 인한 로딩 속도의 장점이 있을 것으로 예상한다. (추가적인 검토가 필요)
@Testcontainers
@Configuration
public class TestContainerConfiguration {
@Container
static MySQLContainer<?> MYSQL_CONTAINER = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"));
static {
MYSQL_CONTAINER.start();
}
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", MYSQL_CONTAINER::getJdbcUrl);
registry.add("spring.datasource.username", MYSQL_CONTAINER::getUsername);
registry.add("spring.datasource.password", MYSQL_CONTAINER::getPassword);
}
@PreDestroy
void preDestroy() {
if (MYSQL_CONTAINER.isRunning()) {
MYSQL_CONTAINER.stop();
}
}
}
(2) 테스트 컨테이너를 사용할 때 ddl-auto option=create 를 사용하기
테스트 컨테이너를 도입하고 테스트가 완료된 후에도 테스트를 종료하지 않는 이슈가 있었다. 결국 테스트가 성공하여 종료는 되었지만 종료하기 위한 시간 지연으로 인한 빌드 시간이 길어졌다.
원인 확인을 위해 로그를 확인해보니 drop table 하는 과정을 진행하다 connection 이 끊어지는 문제였다. drop table 을 하는 과정에서 테스트 컨테이너가 먼저 종료되어 drop table 을 재시도하기 위해 connection 반복 시도하다 결국 실패했다.
drop table 이 실패하면서 Test Container 자원도 회수하지 못해 컨테이너가 삭제되지 못한 것을 확인할 수 있다.
현재 자바 코드를 통해서 TestContainer lifecycle 을 제어하는데 한계가 있어 기존 spring.jpa.hibernate.ddl-auto 옵션을 create-drop 에서 create 로 변경했다. create-drop 은 테스트가 종료되면 drop table 을 호출하는 반면, create 는 테스트가 시작되는 시점에 drop table 을 호출하여 TestContainer 의 lifecycle 을 제어하지 않고 해결할 수 있는 방법이었다.
(4) 이후에 Gradle BuildService 도 한번 검토해보자.
이전에도 언급했다시피 java code level 에서 테스트 컨테이너의 라이프 사이클을 조절하는데 한계가 있다. 이를 개선할 수 있는 방법 중 하나가 Gradle BuildService 라고 한다.
Gradle BuildService 는 task 사이의 상태나 리소스를 공유하게 해주는 인터페이스이다. 이를 통해 Container 를 등록하여 integration test 에서 사용할 수 있게 만들어주면 java code level 보다 더욱 넓은 범위로 TestContainer lifecycle 을 제어가 가능하다. 해당 내용은 이후에 다시 검토해볼 예정이다.
Reference
- [Testcontainer]Getting started with Testcontainers in a Java Spring Boot Project : https://testcontainers.com/guides/testing-spring-boot-rest-api-using-testcontainers/
- [Testcontainer] Testcontainers container lifecycle management using JUnit 5 : https://testcontainers.com/guides/testcontainers-container-lifecycle/
'test' 카테고리의 다른 글
docker -compose 기반 로컬 DB 모니터링 구축(mysqld-exporter + prometheus + grafana) (0) | 2025.01.25 |
---|---|
K6 tutorial (0) | 2024.12.04 |