TIL - 20251129

juni·2025년 11월 28일

TIL

목록 보기
192/316

1129 Spring Boot 테스트 심화: DB 통합 테스트와 Testcontainers


✅ 1. 데이터베이스 테스트의 종류와 한계

  • 데이터베이스 연동 코드를 테스트하는 방법은 크게 두 가지로 나뉩니다.
  1. @DataJpaTest (인메모리 DB 사용):

    • 개념: 실제 DB가 아닌, H2와 같은 인메모리(In-memory) 데이터베이스를 사용하여 Repository 계층을 테스트하는 방식입니다.
    • 장점: 설정이 간편하고, 실행 속도가 빠르며, 각 테스트 후 자동 롤백되어 테스트 간 격리를 보장합니다.
    • 한계점:
      • SQL 방언(Dialect)의 차이: 인메모리 DB(H2)의 SQL 문법과 실제 운영 DB(MySQL, PostgreSQL 등)의 문법이 미묘하게 다릅니다. H2에서는 성공한 쿼리가 실제 DB에서는 실패할 수 있습니다.
      • DB 고유 기능 테스트 불가: 실제 DB가 제공하는 고유한 함수나 데이터 타입 등을 테스트할 수 없습니다.
      • 결론: @DataJpaTest는 간단한 CRUD를 검증하는 데는 유용하지만, 운영 환경과의 완벽한 동일성을 보장하지는 못합니다.
  2. @SpringBootTest (실제 DB 연결):

    • 개념: 테스트용 로컬 DB나 개발 DB에 직접 연결하여 통합 테스트를 수행하는 방식입니다.
    • 장점: 운영 환경과 가장 유사한 환경에서 테스트하여 신뢰도가 높습니다.
    • 한계점:
      • 환경 의존성: 모든 개발자와 CI 서버에 동일한 버전의 DB를 설치하고 설정해야 하는 번거로움이 있습니다.
      • 테스트 격리 문제: 테스트 실행 시 DB 상태가 영구적으로 변경되어, 다른 테스트에 영향을 줄 수 있습니다. (@Transactional로 롤백할 수 있지만 한계가 있음)

✅ 2. Testcontainers: 테스트를 위한 Docker 컨테이너

  • Testcontainers는 JUnit 테스트 코드 내에서 Docker 컨테이너를 프로그래매틱하게 생성하고 관리할 수 있게 해주는 Java 라이브러리입니다.

  • 핵심 아이디어: 테스트가 실행될 때마다, 필요한 소프트웨어(DB, Redis, Kafka 등)가 설치된 Docker 컨테이너를 즉석에서 띄우고, 테스트가 끝나면 컨테이너를 자동으로 파기합니다.

➕ Testcontainers의 장점

  1. 운영 환경과의 동일성: 실제 운영 환경에서 사용할 동일한 버전의 데이터베이스(e.g., MySQL 8.0) Docker 이미지를 사용하여 테스트하므로, SQL 방언 차이 등의 문제를 원천적으로 해결합니다.
  2. 환경 독립성: 개발자의 로컬 머신이나 CI 서버에 별도로 데이터베이스를 설치할 필요가 없습니다. 오직 Docker만 설치되어 있으면 됩니다.
  3. 완벽한 테스트 격리: 각 테스트 클래스(또는 메서드)마다 깨끗하고 독립적인 데이터베이스 컨테이너를 띄워서 사용하므로, 테스트 간에 데이터가 오염될 걱정이 전혀 없습니다.

✅ 3. @SpringBootTest와 Testcontainers를 이용한 통합 테스트

  • @SpringBootTest의 "실제 환경과 유사함"이라는 장점과, Testcontainers의 "환경 독립성 및 격리"라는 장점을 결합하여, 가장 이상적인 데이터베이스 통합 테스트 환경을 구축할 수 있습니다.

➕ 구현 흐름

  1. 의존성 추가: build.gradletestcontainers와 사용할 데이터베이스(e.g., mysql)에 대한 의존성을 추가합니다.

  2. 테스트 컨테이너 설정:

    • @SpringBootTest가 붙은 테스트 클래스에 @Testcontainers 어노테이션을 추가하여 Testcontainers 기능을 활성화합니다.
    • @Container 어노테이션을 사용하여, 테스트 중에 실행할 Docker 컨테이너를 정의합니다. MySQLContainer와 같은 공식 지원 모듈을 사용하면 설정이 매우 간편합니다.
    • @DynamicPropertySource를 사용하여, 동적으로 생성된 컨테이너의 접속 정보(JDBC URL, username, password)를 Spring Boot의 application.yml 설정값에 런타임에 주입합니다.
    @SpringBootTest
    @Testcontainers // Testcontainers 활성화
    class MyIntegrationTest {
    
        // static으로 선언하여 클래스 내 모든 테스트에서 컨테이너를 공유
        @Container
        static MySQLContainer<?> mySQLContainer = new MySQLContainer<>("mysql:8.0");
    
        // 동적으로 생성된 컨테이너의 접속 정보를 Spring 설정에 주입
        @DynamicPropertySource
        static void configureProperties(DynamicPropertyRegistry registry) {
            registry.add("spring.datasource.url", mySQLContainer::getJdbcUrl);
            registry.add("spring.datasource.username", mySQLContainer::getUsername);
            registry.add("spring.datasource.password", mySQLContainer::getPassword);
        }
    
        @Autowired
        private MyService myService;
    
        @Test
        void integrationTest() {
            // 이 테스트는 실제 MySQL 8.0 Docker 컨테이너 위에서 실행됨
            // ...
        }
    }
  3. 테스트 실행:

    • JUnit 테스트를 실행하면, Testcontainers는 먼저 Docker를 통해 MySQL 컨테이너를 시작합니다.
    • @DynamicPropertySource가 컨테이너의 동적 포트와 자격 증명을 Spring의 DataSource 설정에 덮어씁니다.
    • @SpringBootTest는 이 설정으로 Spring 컨테이너를 구동하고, 실제 DB 커넥션을 맺습니다.
    • 테스트 메서드가 실행됩니다.
    • 모든 테스트가 완료되면, Testcontainers가 자동으로 MySQL 컨테이너를 중지하고 삭제합니다.

📌 요약

  • @DataJpaTest는 빠르지만 운영 환경과의 차이라는 한계가 있고, 실제 DB를 사용하는 @SpringBootTest환경 의존성격리에 어려움이 있습니다.
  • Testcontainers는 테스트 실행 시 Docker 컨테이너를 동적으로 생성하여, 운영 환경과 동일하면서도 완벽하게 격리된 테스트 환경을 제공하는 혁신적인 솔루션입니다.
  • @SpringBootTestTestcontainers를 함께 사용하면, @DynamicPropertySource를 통해 동적으로 생성된 DB 컨테이너의 접속 정보를 Spring에 주입하여, 신뢰도 높고 재현 가능한 데이터베이스 통합 테스트를 작성할 수 있습니다.

0개의 댓글