TestContainers: 테스트에서 도커 컨테이너를 실행할 수 있도록 하는 라이브러리
Testcontainers
Testcontainers About Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container. Testcontainers make the followi
www.testcontainers.org
DB의 경우 DB마다 isolation, propagation 전략들이 다르다. (스프링에서는 DB에서 제공하는 이러한 전략들을 따를 뿐이다)
하지만 컨테이너의 테스트용 디비를 띄우고, 스크립트를 실행하고, 테스트가 끝난 후에는 컨테이너를 정리하는 등 복잡해질 수 있다.
이를 해결하기 위한 방법이 바로 TestContainers이다.
먼저 junit 확장체를 지원하는 testcontainers 모듈을 gradle에 추가한다.
여러 모듈 중에 현재 프로젝트에서 사용하고 있는 DB 모듈을 동일하게 사용하면 된다. 내 경우는 MariaDB를 사용하고 있음.
www.testcontainers.org/modules/databases/mariadb/
MariaDB Module - Testcontainers
www.testcontainers.org
testImplementation "org.testcontainers:junit-jupiter:1.15.1"
testCompile "org.testcontainers:mariadb:1.15.1"
@ActiveProfiles("test")
@Testcontainers
@SpringBootTest
class ToyRepositoryTest {
@Autowired
private ToyRepository toyRepository;
@Autowired
private OrganizationRepository organizationRepository;
static MariaDBContainer mariaDB = new MariaDBContainer().withDatabaseName("toy_land_test");
@BeforeAll
static void beforeAll() {
mariaDB.start();
}
@AfterAll
static void afterAll() {
mariaDB.stop();
}
@Test
/* 테스트 */
}
spring:
jpa:
open-in-view: false
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: create-drop
datasource:
url: jdbc:tc:mariadb:///toy_land_test
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
위 코드의 경우 테스트를 실행시키기 전에 컨테이너를 띄우고 실행 후에는 컨테이너를 없애는 것이 반복되어 느릴 수 있다.
아래 코드처럼 컨테이너를 static으로 띄워두고 테스트를 실행 시키기 전에 DB를 비우는 방식으로 코드를 짤 수도 있다. (위의 경우보다 빠를 것이다.)
package com.openhack.toyland.domain.toy;
import static org.assertj.core.api.Assertions.*;
import com.openhack.toyland.domain.Organization;
import com.openhack.toyland.domain.OrganizationRepository;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.MariaDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@ActiveProfiles("test")
@Testcontainers
@SpringBootTest
class ToyRepositoryTest {
@Autowired
private ToyRepository toyRepository;
@Autowired
private OrganizationRepository organizationRepository;
@Container
static MariaDBContainer mariaDB = new MariaDBContainer().withDatabaseName("toy_land_test");
@BeforeEach
void beforeEach() {
toyRepository.deleteAll();
organizationRepository.deleteAll();
}
@Test
/* 테스트 */
}
TestContainers가 제공하지 않은 일반적인 컨테이너를 만드는 방법
- GenericContainer(dockerImageName): 이미지 이름만 있으면 컨테이너를 만드는 것이 가능하다. 이 경우 먼저 로컬에서 이미지를 찾아보고, 로컬에 없으면 퍼블릭한 원격에서 찾아서 가져온다.
- withEnv: 환경 변수 세팅이 필요하다.
- withExposedPorts: 포트 노출 -> 일반적으로는 컨테이너가 랜덤하게 매핑해준다
- waitingFor: 해당 컨테이너가 띄워졌는지 확인 후 테스트 실행
TestContainers의 로그 살펴보기
- getLogs(): 모든 로그를 보는 방법
- followOutput(new Slf4jLogConsumer(log)): 로그 스트리밍
컨테이너 내부의 정보를 테스트 코드에서 활용하는 방법
ApplicationContextInitializer
스프링 ApplicationContext를 프로그래밍으로 초기화 할 때 사용할 수 있는 콜백 인터페이스로, 특정 프로파일을 활성화 하거나, 프로퍼티 소스를 추가하는 등의 작업을 할 수 있다.
TestPropertyValues
테스트용 프로퍼티 소스를 정의할 때 사용한다.
Environment
스프링 핵심 API로, 프로퍼티와 프로파일을 담당한다.
Testcontainer를 사용해서 생성한 컨테이너에 대해 ApplicationContextInitializer를 구현하여 생성된 컨테이너에서 정보를 Environment에 넣어준다.
그 다음 @ContextConfiguration을 사용해서 아까 구현했던 ApplicationContextInitializer 구현체를 등록해주면, 테스트 코드에서 Environment, @Value, @ConfigurationProperties 등 다양한 방법으로 프로퍼티를 사용할 수 있게 된다.
@ActiveProfiles("test")
@Testcontainers
@Slf4j
@ContextConfiguration(initializers = StudyServiceTest.ContainerPropertyInitializer.class)
class StudyServiceTest {
@Mock MemberService memberService;
@Autowired StudyRepository studyRepository;
@Value("${container.port}") int port;
@Container
static GenericContainer postgreSQLContainer = new GenericContainer("postgres")
.withExposedPorts(5432)
.withEnv("POSTGRES_DB", "studytest");
@BeforeAll
static void beforeAll() {
Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(log);
postgreSQLContainer.followOutput(logConsumer);
}
@BeforeEach
void beforeEach() {
System.out.println("===========");
System.out.println(postgreSQLContainer.getMappedPort(5432));
System.out.println(port);
studyRepository.deleteAll();
}
@Test
/* 테스트 */
static class ContainerPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
TestPropertyValues.of("container.port=" + postgreSQLContainer.getMappedPort(5432))
.applyTo(context.getEnvironment());
}
}
}
만약 테스트 컨테이너들을 여러개를 동시에 띄워서 사용해야 한다면? -> DockerComposeContainer
www.testcontainers.org/modules/docker_compose/
Docker Compose Module - Testcontainers
Docker Compose Module Benefits Similar to generic containers support, it's also possible to run a bespoke set of services specified in a docker-compose.yml file. This is intended to be useful on projects where Docker Compose is already used in dev or o
www.testcontainers.org
static DockerComposeContainer composeContainer =
new DockerComposeContainer(new File("src/test/resources/docker-compose-test.yml"))
.withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30)))
.withExposedService("elasticsearch_1", ELASTICSEARCH_PORT, Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30)));
위와 같이 docker-compose 파일을 주면 여러 컨테이너를 띄울 수 있다. (이때, 호스트쪽 포트 매핑은 안해두는 것이 낫다)
아직 컨테이너가 뜨지도 않았는데 테스트를 실행하는 것을 방지하기 위해 Wait.forListeningPort()를 추가 해 주는 것이 좋다.
위의 경우 docker-compose로 띄운 서비스의 정보를 참조하기 위해 구현하는 ApplicationContextInitializer는 아래와 같이 구현하면 된다.
static class ContainerPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
TestPropertyValues.of("container.port=" + composeContainer.getServicePort("study-db", 5432))
.applyTo(context.getEnvironment());
}
}
'Java (+ Spring)' 카테고리의 다른 글
운영 이슈 테스트: Chaos Monkey (0) | 2021.02.11 |
---|---|
Application의 성능 테스트하기: JMeter (0) | 2021.02.11 |
Mock객체로 테스트 하기: Mockito (0) | 2021.02.10 |
Java Unit Test: Junit5 (0) | 2021.02.10 |
Spring Boot: Spring Security & 카카오 로그인 API(...ing) (0) | 2020.10.06 |