Testcontainers로 테스트 환경 구축하기

왔다 정보리·2025년 10월 8일
0

테스트 코드를 작성하면서 테스트 환경을 구축하는 것에 어려움을 겪었다. 특히 테스트를 할 때는 보통 내장 DB인 H2를 많이 사용하게 되는데, H2를 사용하면 테스트 환경과 운영 환경 사이에 간극이 생기는 경우가 있었다. 이를 해결할 수 있는 방법이 없을까 찾아보다가 Testcontainers라는 것을 알게 되었고, 덕분에 복잡한 설정 없이 테스트 환경을 구축할 수 있었다. Testcontainers에 대해 기억하기 위해 블로그를 작성하고자 한다!


Testcontainers


Testcontainers

도커 컨테이너에서 실행할 수 있는 테스트 라이브러리이다. 도커 컨테이너에서 실행이 되기 때문에 반드시 도커가 설치되어 있어야 하며, 모든 상황에서 일관된 테스트 환경을 보장한다.
또한, 도커 컨테이너를 활용해서 외부 의존성을 포함한 테스트 환경 구축 및 관리를 편리하게 할 수 있다는 장점이 있다. 테스트가 시작되면 자동으로 컨테이너가 실행되고, 테스트가 종료되면 자동으로 컨테이너가 정리된다.

Testcontainers의 장점

  1. 다양한 언어와 테스트 프레임워크를 지원한다.
  2. 복잡한 설정 없이도 컨테이너화된 데이터베이스 인스턴스를 사용하여 테스트를 진행할 수 있다. 로컬에 MySQL이나 PostgreSQL 등의 DB를 직접 설치하지 않아도 되고, 도커만 있으면 실행이 가능하다.
  3. 실제 운영 환경과 동일한 테스트 환경을 구축할 수 있다. H2와 같은 내장 DB가 아닌 실제 DB 컨테이너에서 테스트를 진행하기 때문에 운영 환경에서 발생할 수 있는 문제를 미리 발견할 수 있다.

주의사항

Testcontainers를 사용하기 전에 알아두어야 할 몇 가지 주의사항이 있다.

  1. 도커 컨테이너를 사용하기 때문에 도커 데몬이 실행 중이어야 한다.
  2. 로컬 뿐만 아니라 CI/CD 환경에서도 도커가 사용 가능하도록 설정되어 있어야 한다.
  3. 실행 할 때마다 도커 이미지 다운로드에 의한 시간이 소요될 수 있다. 재사용 옵션을 활성화하면 첫 실행 이후에는 이 시간을 줄일 수 있다.

H2 내장 DB vs Testcontainers


기존 방식(H2)의 문제점

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    username: username
    password: password
    driver-class-name: org.h2.Driver

H2를 사용하면 간편하게 내장 DB를 통해 테스트를 할 수 있지만 문제점이 있다.
MySQL 혹은 PostgreSQL 등의 전용 문법이 H2에서 동작하지 않는 경우가 있다. 반대로 H2에서는 동작하지만, 타DB에서 지원하지 않는 문법도 있다. 따라서 테스트는 통과했으나 운영 환경에서 오류가 발생할 수 있다.

Testcontainers 방식

spring:
  datasource:
    url: jdbc:tc:mysql:8.0:///testdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: username
    password: password
    driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver

하지만 Testcontainers를 사용하면 이와 같은 문제를 해결할 수 있다. yml 파일을 통해 Testcontainers의 DB를 설정하는 코드이다. 이렇게 하면 MySQL 컨테이너에서 테스트가 가능하다. 물론, 설정을 통해 MySQL 외에도 다양한 DB를 사용할 수 있다. 개발자 환경에 DB를 따로 설치하지 않아도 되며 컨테이너를 통해 알아서 처리된다.

Testcontainers 구현하기


1. 의존성 설정

dependencies {
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.testcontainers:testcontainers'
    testImplementation 'org.testcontainers:junit-jupiter'
    testImplementation 'org.testcontainers:mysql'
    testImplementation 'org.testcontainers:jdbc'
}

testImplementation을 사용하여 테스트 범위에만 의존성을 추가한다.

1-1. 의존성 설명

의존성설명
spring-boot-starter-testSpring Boot 테스트 라이브러리
JUnit, AssertJ, Mockito 등 테스트를 위한 기본 라이브러리를 포함한다
testcontainersTestcontainers 코어 라이브러리
junit-jupiter@Testcontainers, @Container 등의 어노테이션을 제공한다
mysqlMySQL 전용 컨테이너 모듈
사용하는 DB에 따라 다른 라이브러리 사용할 수 있다 (예: postgresql, mongodb)
jdbcJDBC URL을 통한 간편한 컨테이너 실행을 지원한다

2. TestContainerConfig

@TestConfiguration
@Testcontainers
public class TestContainerConfig {
    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
            .withDatabaseName("testdb")
            .withUsername("username")
            .withPassword("password")
            .withReuse(true); // 컨테이너 재사용 활성화

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", mysql::getJdbcUrl);
        registry.add("spring.datasource.username", mysql::getUsername);
        registry.add("spring.datasource.password", mysql::getPassword);
        registry.add("spring.datasource.driver-class-name", () -> "com.mysql.cj.jdbc.Driver");
    }

    @Bean
    public MySQLContainer<?> mysqlContainer() {
        return mysql;
    }
}

Testcontainers 설정을 위한 Config 파일을 작성한다.
Config 파일을 작성해도 되고, 위처럼 yml 파일을 통해 Testcontainers 설정을 할 수도 있다.

2-1. 어노테이션 설명

어노테이션상세 설명
@TestConfiguration테스트 설정 클래스 지정
테스트 실행 시에만 로드되도록 한다
@TestcontainersTestcontainers의 생명주기를 관리한다
@Container관리해야 하는 컨테이너임을 표시한다
static으로 선언하면 클래스 레벨에서 컨테이너를 공유할 수 있어, 여러 테스트 메서드에서 동일한 컨테이너를 사용할 수 있다
이렇게 하지 않으면 테스트 메서드가 실행될 때마다 새로운 컨테이너가 생성되어 테스트 속도가 느려질 수 있다
@DynamicPropertySource컨테이너 실행 이후에 동적으로 생성되는 정보를 환경변수로 주입한다
주입된 설정으로 실제 DB에 연결하여 테스트를 수행한다

2-2. TestContainerConfig 동작 원리

  1. 테스트 실행 시 @Testcontainers@Container 필드를 찾아 MySQL 컨테이너를 시작한다.
  2. 컨테이너 시작 후 랜덤 포트가 할당된다.
  3. @DynamicPropertySource가 동적 정보를 Spring에 주입한다.
  4. 테스트 코드에서 주입된 설정으로 실제 MySQL을 연결한다.
  5. 모든 테스트 종료 후 컨테이너는 자동으로 정리된다.

3. 테스트 작성 - E2E 테스트

@SpringBootTest
@ContextConfiguration(classes = TestContainerConfig.class)
class HealthControllerTest {

    @Test
    void healthCheck() {
        ExtractableResponse<Response> response = RestAssured.given().log().all()
                .when()
                .get("/health/check")
                .then().log().all()
                .extract();

        assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
    }
}

@ContextConfiguration을 통해 앞서 작성한 Testcontainers 설정을 주입한다. 이렇게 하면 MySQL 컨테이너에 연결되어 테스트가 실행된다. H2가 아닌 실제 MySQL DB에서 테스트를 진행하기 때문에 운영 환경과 동일한 조건에서 검증이 가능하다.

추가) 성능 최적화 - 컨테이너 재사용

컨테이너 재사용이 필요한 이유

Testcontainers는 기본적으로 테스트를 진행할 때마다 새 컨테이너를 생성한다. 테스트를 실행할 때마다 컨테이너 생성/삭제로 인한 오버헤드가 발생하는데, 재사용 옵션을 통해 이를 개선할 수 있다.

컨테이너 재사용 설정 - Config 파일

@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
        .withDatabaseName("testdb")
        .withUsername("username")
        .withPassword("password")
        .withReuse(true);

Config 파일에서 withReuse() 옵션을 통해 컨테이너 재사용 옵션을 추가할 수 있다. withReuse(true)로 설정하면 첫 실행에는 컨테이너를 생성하고, 이후 실행에서는 이미 만들어진 기존 컨테이너를 재사용한다.

컨테이너 재사용 설정 - yml 파일

testcontainers:
  reuse:
    enable: true

Config 파일과 마찬가지로 yml 파일에서도 컨테이너 재사용 옵션을 추가할 수 있다.

마치며


Testcontainers를 통해 yml 혹은 Config 파일을 정의하여 편리하게 테스트 환경을 구축할 수 있었다. Testcontainers를 사용하면 실제 운영 환경과 동일한 DB에서 테스트를 할 수 있어, 운영 환경에서 발생할 수 있는 문제를 사전에 발견할 수 있다. 이번에는 단일 모듈에서 테스트 코드를 작성하여 테스트 의존성을 따로 관리하지 않아도 괜찮았지만 멀티 모듈 환경에서는 TestFixtures를 통해 하위 모듈에 의존성을 전파해주어야 한다. 이 방식도 정리하고자 한다!

참고 자료

Testcontainers
The simplest way to replace H2 with a real database for testing
MySQL Module - Testcontainers for Java
Testcontainers로 통합테스트 만들기
TestContainers로 유저시나리오와 비슷한 통합테스트 만들어 보기
Spring Boot에서 Testcontainers로 통합 테스트 환경 구축하기

profile
왔다 정보리

0개의 댓글