Programming-[Backend]/SpringBoot

testcontainer 자바 도커 컨테이너 테스트: @ExtendWith, @DirtiesContext

컴퓨터 탐험가 찰리 2024. 6. 2. 08:17
728x90
반응형

 

 

1. testcontainer 개요

 

testcontainer는 테스트 시에만 사용할 수 있는 컨테이너를 실행시켜준다. 애플리케이션 실행에는 필요없지만 테스트만을 위해 컨테이너가 필요한 경우 해당 이미지를 구동시켜주는 것이다. 만약 테스트를 위해 MySQL DB가 필요하다면 testcontainer가 없는 경우 SpringBoot가 제공하는 In-memory DB를 이용하거나 따로 DB용 컨테이너를 실행해야할텐데, testcontainer는 그러한 과정 대신 테스트가 실행될 때만 MySQL용 컨테이너를 띄워주고 테스트가 끝나면 해당 컨테이너를 종료시킨다.

 

build.gradle에 아래처럼 추가해준다.

dependencies {
	...
    testImplementation "org.testcontainers:mysql:1.19.8"
    testImplementation "org.testcontainers:kafka:1.19.8"
    testImplementation "org.testcontainers:testcontainers:1.19.8"
    testImplementation 'org.testcontainers:junit-jupiter:1.19.8'
}

 

MySQL과 Kafka를 사용하기 위해서 따로 추가해주면 된다. 

 

이외에도 redis 등 많은 모듈을 지원한다. 공식 홈페이지의 Docs -> Java -> Modules 부분을 보면 어떤 컨테이너들이 제공되는지 확인할 수 있다.

https://java.testcontainers.org/

 

Testcontainers for Java

Testcontainers for Java Not using Java? Here are other supported languages! About Testcontainers for Java Testcontainers for Java is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web brow

java.testcontainers.org

 

 

 

 

2. testcontainer 사용법

 

통합 테스트를 예시로 든다. @ExtendWith를 사용하여 Extension으로 정의한 클래스들을 불러오면 된다. 그리고나서 실행하면 docker desktop이나 CLI 도구로 컨테이너가 실행되는 것을 확인할 수 있다.

@SpringBootTest
@ExtendWith({KafkaExtension.class, RedisExtension.class})
class KafkaConsumerTest {
...
}

 

Extension 클래스들은 예를 들어 아래와 같이 만들면 된다. 상세한 옵션들은 공식 홈페이지 docs를 참고하면 된다.

public class MySQLExtension implements BeforeAllCallback, AfterAllCallback {

  private MySQLContainer<?> mysqlContainer;

  @Override
  public void beforeAll(ExtensionContext context) {
    mysqlContainer =
        new MySQLContainer<>("mysql:8")
            .withDatabaseName("yourDataBaseName")
            .withUsername("yourUserName")
            .withPassword("yourPassword");

    mysqlContainer.start();

    System.setProperty("spring.datasource.url", mysqlContainer.getJdbcUrl());
    System.setProperty("spring.datasource.username", mysqlContainer.getUsername());
    System.setProperty("spring.datasource.password", mysqlContainer.getPassword());
  }

  @Override
  public void afterAll(ExtensionContext context) {
    if (mysqlContainer != null) {
      mysqlContainer.stop();
    }
  }
}

 

 

beforeAll과 afterAll로 container가 실행되기 전, 후에 어떻게 작동할지를 설정해줄 수 있다.

 

 

 

 

3. 추가 내용

 

기본적인 것은 알았으니 추가적인 내용들은 공부하면 된다. 경험상 배운 부분들을 기록해놓는다.

 

 

- MySQL의 경우 기본적으로 싱글톤으로 뜬다.

여러 테스트에 MySQLExtension을 추가하고 실행해보면 컨테이너가 1개만 뜬 채로 테스트들이 진행되는 것을 볼 수 있다. 이렇게 하면 하나의 테스트에서 DB에 데이터를 insert하는 경우 DB 자체가 오염되어 있게 되므로 다른 테스트에 영향을 줄 수도 있다. 테스트간 데이터 격리를 위해서 MySQL을 테스트마다 따로 띄우는게 필요할 수 있다.

 

테스트마다 MySQL 컨테이너를 따로 실행하는 경우, 당연히 컨테이너 구동 시간이 추가됨에 따라 테스트 시간 및 빌드 시간이 길어질 수 있다는 단점을 알고 있어야한다. 그리고 스프링은 Spring Context를 하나만 실행해서 모든 테스트에 공통으로 적용하는데, DB가 종료되는 경우 Spring Context에서 DB의 connection pool을 잡지 못해서 Connection 에러가 날수도 있다.

https://stackoverflow.com/questions/59372048/testcontainers-hikari-and-failed-to-validate-connection-org-postgresql-jdbc-pgc

 

이 경우 위 글처럼 @DirtiesContext를 적용하라고 하는데, Spring Context를 바꿔주는 옵션이라 테스트가 더 느려질 수도 있다.

 

 

-tc 설정

테스트용 profile 설정에 datasource에 testcontainer를 활용하는 driver-class-name을 지정하고, url에 tc라는 문구를 추가해주면 테스트마다 @ExtendWith 내에 MySQLExtension을 따로 지정해줄 필요가 없다.

spring:
  config:
    activate:
      on-profile: "test-profile"
  jpa:
    ...

  datasource:
    driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
    url: jdbc:tc:mysql:8://yourDbSchema
    username: yourUserName
    password: yourPassword

 

 

 
728x90
반응형