본문 바로가기

SearchDeveloper/애플리케이션테스트

TestContainers 설명

tetcontainers

이 글은 온라인 강의를 듣고 해당 내용을 직접 실습해보며 정리한 글입니다. 더 자세한 내용은 아래 강의에서 확인할 수 있습니다.

더 자바, 애플리케이션을 테스트하는 다양한 방법 - 인프런 | 강의(백기선)

 

테스트에서 도커 컨테이너를 실행할 수 있는 라이브러리
  • 테스트 실행시 DB를 설정하거나 별도의 프로그램 또는 스크립트를 실행할 필요 없다.
  • 보다 Production에 가까운 테스트를 만들 수 있다.
  • 테스트가 느려진다.
  • https://www.testcontainers.org/
→ 테스트컨테이너를 쓰면 테스트용 도커를 만들 필요도, 손수 띄우고 내릴 필요도 없다.
 

설치하기

디펜던시
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.15.1</version>
    <scope>test</scope>
</dependency>​
junit-jupiter를 지원하는 testcontainers
 
독스에 설치할 컨테이너 별로 디펜던시를 확인할 수 있다.
ex) postgresql 전용 디펜던시로 postgres 컨테이너를 띄울 수 있다.
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.15.1</version>
    <scope>test</scope>
</dependency>​
 
 

테스트 컨테이너 선언하기

@SpringBootTest
@ActiveProfiles("test")
@Slf4j
class StudyServiceMockContainerTest {

    static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:9.6.12")
            .withDatabaseName("studytest")
            ; 

    @BeforeAll
    static void beforeAll() {
        postgreSQLContainer.start();
        log.info(postgreSQLContainer.getJdbcUrl()); // jdbc:postgresql://localhost:32769/test?loggerLevel=OFF
    }

    @AfterAll
    static void afterAll() {
        postgreSQLContainer.stop();
    }
}
  • static PostgreSQLContainer postgreSQLContainer : static 은 안 붙이면 각 @Test 마다 컨테이너가 재시작된다. static을 붙이면 모든 @Test 에서 컨테이너를 재사용할 수 있다.
  • @BeforeAll, @AfterAll : 테스트 클래스 시작 전에 컨테이너를 올려주고 모든 테스트가 끝나면 컨테이너를 내려준다.
 

테스트 컨테이너 선언하기 - 좀 더 간단하게

@SpringBootTest
@Testcontainers // 확장체 지원하는 composed annotation
class StudyServiceMockContainerTest {
    @Container
    static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:9.6.12")
            .withDatabaseName("studytest")
            ; // 모든 테스트에서 공유하기 위해 static 선언

}
  • @Testcontainers:  @Container 가 붙은 컨테이너 객체를 찾아 라이프사이클 관리 메소드를 실행 해준다.
  • @Container: 컨테이너임을 알리는 표식. static 필드면 모든 테스트에 같은 컨테이너를 재사용하고, static 이 아니면 각 테스트마다 컨테이너를 재시작한다.
 

테스트컨테이너가 띄운 컨테이너를 스프링 설정파일에서 알아차리게 하는 법

application-test.properties
spring.datasource.url=jdbc:tc:postgresql:///studytest
spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
1. url에 jdbc 뒤에 tc를 붙여준다.
2.textcontainers 가 제공하는 드라이버를 지정한다.
 
testcontainer 관점에서 ip port 는 중요하지 않다.  jdbc:mysql:5.7.34://localhost:3306/databasename 와 jdbc:mysql:5.7.34:///databasename 를 같은 URI 로 인식한다.
ContainerDatabaseDriver 가 jdbc:tc: 가 포함된 URI 를 찾아 알맞게 커넥션 시켜준다.
 

Container 기능 살펴보기

 
※ PostgreSQLContainer 처럼 특정 서비스에 종속된 Container 가 아닌 이미지명만으로 Container 생성하기
@Container
static GenericContainer container = new GenericContainer("postgres:9.6.12");
로컬에서 해당 이미지를 찾고 없으면 public 저장소에서 이미지를 다운받는다.
 

Container 옵션 설정 

@Container
static GenericContainer container = new GenericContainer("postgres:9.6.12")
        .withExposedPorts(5432)
        .withEnv("POSTGRES_DB", "studytest")
        .waitingFor(Wait.forListeningPort())
        .waitingFor(Wait.forHttp("/hello")) 
        .waitingFor(Wait.forLogMessage("regex", 1))
        ;
  • .withExposedPorts(5432) : 컨테이너 내에서 사용할 포트 지정
  • .withEnv("POSTGRES_DB", "studytest") : 환경변수 설정

Container 정보 확인

@BeforeAll
static void beforeAll() {
    log.info("host port: {}", container.getMappedPort(5432)); 
    log.info("getLogs(): {}", container.getLogs());
    container.followOutput(new Slf4jLogConsumer(log));
}
  • container.getMappedPort(5432) : 컨테이너 포트 5432 에 바인딩된 호스트 포트 확인
  • container.getLogs() : 컨테이너 안 로그  string 으로 받기
  • container.followOutput(new Slf4jLogConsumer(log)) : 로그 streaming 으로 받기
 

Container 정보를 스프링 프로퍼티로 등록하기

: 스프링 컨텍스트에 컨테이너 정보를 프로퍼티로 넣고 스프링을 통해 프로퍼티 접근하기
@SpringBootTest
@ContextConfiguration(initializers = StudyServiceMockContainerTest.ContainerPropertyInitializer.class)
@Testcontainers 
class StudyServiceMockContainerTest {
    @Autowired
    Environment environment;

    @Value("${container.port}")
    int port;

    @Container
    static GenericContainer container = new GenericContainer("postgres:9.6.12")// 지원하는 클래스 없을 때 GenericContainer 로 생성 가능. 로컬에서 이미지 찾고 없으면 땡겨온다.
            .withExposedPorts(5432) // 컨테이너 내 포트
            ;

    @BeforeEach
    void beforeEach() {
        log.info("host port by spring environment: {}", environment.getProperty("container.port"));
        log.info("host port by spring value: {}", port);
    }


    @Test
    public void createNewStudy() {
    }

    static class ContainerPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            TestPropertyValues.of("container.port=" + container.getMappedPort(5432))
            .applyTo(applicationContext.getEnvironment())
            ;
        }
    }
}
  • ContainerPropertyInitializer : 프로퍼티 추가
  • ApplicationContextInitializer : 이 인터페이스를 구현함으로써 프로퍼티를 추가할 수 있다. 스프링부트가 아닌 스프링 코어에서 제공하는 콜백 인터페이스. ApplicationContext 를 초기화하는 과정에서 실행
  • @ContextConfiguration : 스프링이 테스트 컨텍스트 만들 때 우리가 만든 ContainerPropertyInitializer 도 추가할 수 있도록 설정해줌 
 

Docker-compose 사용하기

docker-compose 사용하면: 도커 컨테이너 동시에 띄울 때 그들간의 관계, 순서 등을 정의할 수 있고 같이 관리할 수 있다.
 

Docker-compose container 띄우기

@Container
static DockerComposeContainer dockerComposeContainer = new DockerComposeContainer(new File("src/test/resources/docker-compose.yml"));

 

docker-compose.yml
version: "3"

services:
  study-db:
    image: postgres
    ports:
      - 5432
    environment:
      POSTGRES_DB: study
      POSTGRES_USER: study
      POSTGRES_PASSWORD: study
디펜던시 1.12.4 부터 docker-compose.yml 형식에 맞게 정상작동한다.
 
docker-compose는 느리기 때문에 다 띄워져야 테스트를 실행할 수 있도록 wait을 걸어주는 것이 좋다.
 

Docker-compose Container 정보를 스프링 프로퍼티로 등록하기

@SpringBootTest
@ContextConfiguration(initializers = StudyServiceMockContainerTest.ContainerPropertyInitializer.class)
@Testcontainers 
class StudyServiceMockContainerTest {
    @Autowired
    Environment environment;

    @Value("${container.port}")
    int port;

    @Container
    static DockerComposeContainer dockerComposeContainer = new DockerComposeContainer(new File("src/test/resources/docker-compose.yml"))
            .withExposedService("study-db", 5432)
            ;

    @BeforeEach
    void beforeEach() {
        log.info("host port by spring environment: {}", environment.getProperty("container.port"));
        log.info("host port by spring value: {}", port);
    }


    @Test
    public void createNewStudy() {
    }

    static class ContainerPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            TestPropertyValues.of("container.port=" + dockerComposeContainer.getServicePort("study-db", 5432))
            .applyTo(applicationContext.getEnvironment())
            ;
        }
    }
}
  • .withExposedService("study-db", 5432) : 특정서비스를  expose 해줘야한다.
 

TestContainers 설명

 

글 읽어주셔서 언제나 감사합니다. 좋은 피드백, 개선 피드백 너무나도 환영합니다.

 

연관글

2022.06.21 - [프로그래머/Java] - Java 에서 테스트용 도커 컨테이너 띄우는 법 : TestContainer