도커와 테스트 (TestContainers)
테스트를 위해서는 운영과 동일한 형태의 개발 환경에서 테스트 하는 것이 중요하다. 하지만 매번 동일하게 환경을 구축할 수 없고 모든 개발 자들과 같은 환경을 맞추기도 쉽지 않다.
그래서 Docker를 이용해서 테스트마다 테스트를 위한 컨테이너를 실행시켜서 테스트하고 컨테이너를 제거해주면 좋은데 그런 기능을 TestContainer를 이용해서 가능하다. 실제로 이번에 이직한 회사에서 동일하게 테스트 환경을 사용하는 것을 봤다. 막연하게 그 기능을 사용할 수 있었지만 이번 Junit5 백기선님 강의를 들어서 확실하게 정리할 수 있어서 좋았다. 역시 듣길 잘했다. 꼭 들어보길 강추한다. 그럼 그 내용을 정리해보자.
테스트 컨테이너(Test Container) 라이브러리 추가 및 설정
https://www.testcontainers.org를 보고 테스트에 필요한 모듈의 라이브러리를 추가하면 된다. 나는 gradle을 사용하여 mysql을 사용해야 하기에 다음과 같이 추가했다.
testCompile "org.testcontainers:testcontainers:1.12.4"
testCompile "org.testcontainers:junit-jupiter:1.12.4"
testCompile "org.testcontainers:mysql:1.12.4"
그리고 생성된 mysql test container에서 사용할 설정 값들에 대한 설정이 필요한대 기존 설정과는 다르게 다음과 같이 별도의 jdbc url을 적어줘야한다. 이도 testcontainer 홈페이지에 모듈 설명에 나와있다.
spring:
datasource:
url: jdbc:tc:mysql:5.7.22://localhost:3306/test
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
dbcp2:
driver-class-name: com.mysql.jdbc.Driver
test-on-borrow: true
validation-query: SELECT 1
max-total: 1
jpa:
show-sql: true
테스트 수행
라이브러리와 연결에 대한 설정을 모두 완료하였으면 테스트를 진행해야한다. 테스트 컨테이너를 이용해서 테스트를 진행하기 위해서는 클래스에 @TestContainers 어노테이션을 붙이고 불러들인 MysqlContainer에 어노테이션 @Container를 붙여주면 된다. 그리고 container가 테스트가 실행될 때 시작하고 종료되면 같이 끝나기 위해서 @BeforeAll과 @AfeterAll을 지정해준다.
package com.wedul.javajunit5studyjunit.docker;
import org.junit.jupiter.api.DisplayName;
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.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import javax.transaction.Transactional;
import static org.assertj.core.api.Assertions.*;
/**
* java-junit5-study
*
* @author wedul
* @since 2019/12/24
**/
@ActiveProfiles("test")
@SpringBootTest
@Testcontainers
class StudentServiceTest {
@Autowired
StudentService studentService;
@Container
static MySQLContainer mariaDBContainer = new MySQLContainer();
@Test
@DisplayName("학생 추가하기")
@Transactional
void create_student_test() {
studentService.createStudent(Student.builder()
.studentId(2L)
.age(10)
.name("wedul")
.address("seoul jamsil")
.build()
);
}
@DisplayName("student 조회 테스트")
@Test
@Transactional
void find_student_test() {
studentService.createStudent(Student.builder()
.studentId(2L)
.age(10)
.name("wedul")
.address("seoul jamsil")
.studentNickname("duri")
.build()
);
Student student = studentService.getStudent(2L);
assertThat(student).isNotNull();
}
}
만약 별도록 MysqlContainer처럼 지정이 되어 있지 않은 컨테이너를 올려서 테스트 하고 싶을 때는 GenericContainers를 사용하여 공식 이미지를 다운받아서 사용할수도 있다. 자세한건 강의를 참조하면 좋다.
@Container
static GenericContainer genericContainer = new GenericContainer("mysql");
Docker container 값을 스프링 value로 사용하기
서비스로 올라간 Docker container에 속성을 spring에서 사용하고 싶을때는 Application의 설정값을 읽어서 사용할수 있도록 해주는 ApplicationContextInitializer를 구현하여 값을 application에 전달하여 사용할 수 있다.
우선 ApplicaionContextInitializer를 구현하여 컨테이너 속성 값을 environment에 넘겨주고 이를 스프링 value로 꺼내서 사용하면 된다.
@ActiveProfiles("test")
@SpringBootTest
@Testcontainers
@ContextConfiguration(initializers = StudentServiceTest.ContainerPropertyInitializer.class)
class StudentServiceTest {
@Autowired
StudentService studentService;
@Container
static MySQLContainer mySQLContainer = new MySQLContainer();
@Value("${container.databaseName}")
private String databaseName;
@Test
@DisplayName("학생 추가하기")
@Transactional
void create_student_test() {
System.out.println(databaseName);
studentService.createStudent(Student.builder()
.studentId(2L)
.age(10)
.name("wedul")
.address("seoul jamsil")
.build()
);
}
@DisplayName("student 조회 테스트")
@Test
@Transactional
void find_student_test() {
studentService.createStudent(Student.builder()
.studentId(2L)
.age(10)
.name("wedul")
.address("seoul jamsil")
.studentNickname("duri")
.build()
);
Student student = studentService.getStudent(1L);
assertThat(student).isNotNull();
}
static class ContainerPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertyValues.of("container.databaseName=" + mySQLContainer.getDatabaseName())
.applyTo(applicationContext.getEnvironment());
}
}
}
Docker Compose를 사용하여 테스트하기
매번 새로운 설정을 프로그램상이나 yml으로 정의하는건 너무 번거롭다. 그래서 생성할 컨테이너들을 한번에 기재해서 컨테이너로 올릴 때 사용하는 docker compose를 사용하면 편리하다. 간단하게 mysql 컨테이너를 올릴 yml을 만들고 테스트에서 사용해보자. docker-compose 파일을 읽어들일 때는 DockerComposeContainer를 사용해서 올리면 된다.
### docker-compose.yml 파일
version: '3.1'
services:
maria:
image: mariadb:latest
restart: "always"
ports:
- "13306:3306"
environment:
- MYSQL_ROOT_PASSWORD=test
- MYSQL_DATABASE=test
- MYSQL_USER=wedul_dev
- MYSQL_PASSWORD=test
// 사용방법
@Container
static DockerComposeContainer composeContainer =
new DockerComposeContainer(new File("src/test/resources/docker-compose.yml"));
그리고 위에서는 host에서 연결할 port 13306을 지정하였으나 실제로는 지정하지 않고 하면 랜덤포트를 자유롭게 이용하기 때문에 더 좋을 것 같다. 왜냐하면 해보니까 내가 지정한 포트가 다른 개발자 컴퓨터에서는 이미 다른 컨테이너로써 운영중일 수도 있기 때문에 랜덤 포트를 사용하게 하는게 좋은 것 같다.
ports:
- "3306"
근데 사용해보니 단점이 있다. container_name과 같이 몇개의 docker-compose에서 제공하는 몇개의 부가 속성들을 제대로 읽어들이지 못하고 오류를 뱉어내기도 한다. 다음과 같이 에러가 발생되어서 지웠다 ㅜㅜ
docker-compose.yml has 'container_name' property set for service 'maria' but this property is not supported by Testcontainers, consider removing it
docker-compose를 매번 테스트할때마다 레포지토리에 dev-tool에 놔뒀다가 사용했었는데 확실히 편해지고 좋을 것 같다. 잘 활용해보자.
출처 : https://www.inflearn.com/course/the-java-application-test
github : https://github.com/weduls/junit5
'web > Junit' 카테고리의 다른 글
Spring Junit5 test Mockito (백기선님 인프런 강의) (0) | 2019.12.23 |
---|---|
Spring BootJunit5 테스트 (백기선님 인프런 강의) (0) | 2019.12.23 |
Junit 정리 - 서비스 테스트 하기 (1) | 2018.05.27 |
Junit 정리 - MockMvc를 이용한 컨트롤러 테스트 (6) | 2018.05.27 |