Testcontainers에 대해 소개
개요
테스트 환경은 프로젝트 설정을 할 때 가장 중요한 부분 중 하나이다. 테스트를 함에 있어서 운영과 동일한 형태의 테스트 환경에서 테스트를 하는 것이 중요하기 때문이다.
그러나, 매번 동일하게 환경을 구축할 수 없고 모든 개발 자들과 같은 환경을 맞추기도 쉽지 않다.
그러기 위해서는 테스트 코드를 실행 시킬 때마다 Docker를 사용을 하여 테스트를 위한 컨테이너를 실행시켜, 테스트을 진행을 하고, 끝난 이후에는 컨테이너를 종료 및 제거해주면 좋을 것이다. 그런 기능을 Testcontainer를 이용해서 가능하다.
Testcontainers이란?
Testcontainers는 테스트를 위해 Docker Container 를 실행시켜주는 자바 라이브러리이다.
Docker를 활용한 Infrastructure 구성할 수 있는데, 매우 다양한 서비스 지원한다.
FIRST 원칙 중에 Repeatable 원칙을 지킬 수 있게 해준다.
Testcontainers 관련 사이트
- 공식 문서: https://www.testcontainers.org/
- GitHub: https://github.com/testcontainers/testcontainers-java/
Testcontainers은 왜 사용해야 하나?
테스트 환경은 프로젝트 설정을 할 때 가장 중요한 부분 중에 하나이다.
가장 어렵고 귀찮은 작업이기도 하지만 처음 한번만 잘해 놓으면 추후에 테스트 작성 시에 걱정 없이 아주 깔끔한 테스트 코드를 짤 수 있게 된다. 하지만 그만큼 프로젝트 환경 설정에서 가장 많은 시간을 들이게 되고, 많은 시행착오를 겪는 구간 중 하나라고 볼 수 있다.
Testcontainers는 MySQL, PostgreSQL, Redis, Apache Kafka, Amazon SQS, 그밖에 기타 등의 시스템을 Docker 컨테이너로 만들어 테스트할 수 있는 환경을 제공해 준다.
Testcontainers가 없다면, 실제 시스템 환경을 만들어 줘야 하고, 테스트를 진행할 때마다 테스트 데이터를 초기화해줘야 하는 과정이 필요할 것이다. 또, 테스트를 위해 만든 시스템 환경에 다른 사람이 동시에 접근을 하게 되었을 때에, 다른 테스트로 인해 테스트를 실패할 수 있게 된다. 혹은 외부 모듈로 인해 테스트가 간헐적으로 실패할 수 있다.
즉, 상황에 따라 혹은 외부에 영향으로 여러 결과가 바뀔 수 있다는 것을 의미하는데, 이럴 경우 실패 구간을 찾기 매우 어렵다는 특징을 가지고 있다. 이런 경우에는 멱등성(idempotent)이 유지가 되지 않는다고 하는데,테스트에서 매우 중요한 개념이다. 바로 이 멱등성을 지키는 것이 테스트 전체의 생산성에 아주 큰 영향을 주기 때문이다.
멱등성(idempotent)이란?
연산을 여러 번 적용하더라도 결과가 바뀌지 않는 성질을 뜻한다.쉽게 말해서 함수(혹은 API)를 여러 번 실행하더라도 늘 같은 결과가 나와야 한다는 의미이다.
다양한 Module 지원
Testcontainers는 다양한 모듈을 지원하는데, 그중에 몇개만 소개 하면 아래와 같다.
Testcontainers 사용하기
여기서 간단히 사용 방법에서 대해서 설명하겠다.
라이브러리 추가
Gradle에 JUnit 5에서 사용하려면 아래와 같이 라이브러리가 필요하다.
testImplementation "org.junit.jupiter:junit-jupiter:5.8.1"
testImplementation "org.testcontainers:testcontainers:1.17.3"
testImplementation "org.testcontainers:junit-jupiter:1.17.3"
테스트 코드 작성
@Testcontainers
public class RedisBackedCacheIntTest {
private RedisBackedCache underTest;
// container {
@Container
public GenericContainer redis = new GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
.withExposedPorts(6379);
// }
@BeforeEach
public void setUp() {
String address = redis.getHost();
Integer port = redis.getFirstMappedPort();
// Now we have an address and port for Redis, no matter where it is running
underTest = new RedisBackedCache(address, port);
}
@Test
public void testSimplePutAndGet() {
underTest.put("test", "example");
String retrieved = underTest.get("test");
assertThat(retrieved).isEqualTo("example");
}
}
@Testcontainers
테스트 중에 Redis 컨테이너를 실행하기 위해서는 먼저 @Testcontainers
가 테스트 클래스에 지정이되어야 한다.
@Testcontainers
public class RedisBackedCacheIntTest {
컨테이너 생성
@Container
public GenericContainer redis = new GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
.withExposedPorts(6379);
@Container
주석은 테스트 라이프 사이클의 다양한 이벤트에 대해 이 필드에 알리도록 JUnit에 지정한다.
여기서는 Testcontainers GenericContainer
이며 Docker Hub의 특정 Redis 이미지를 사용하도록 설정하였고, 지정한 포트(6379)를 공개하도록 설정하였다.
코드가 컨테이너와 통신할 수 있는지 확인
컨테이너의 주소를 Testcontainers에 요청한다.
String address = redis.getHost();
컨테이너의 포트를 Testcontainers에 요청한다.
Integer port = redis.getFirstMappedPort();
Container 재사용 (options)
Testcontainers를 사용하게 되면, 테스트를 실행할 때마다 docker Container가 매번 새로 생성되게 되는데, 그럴때마다 매우 시간이 오래 걸린다.
이를 해결하기 위해 Testcontainers에서는 아직 공개되지 않은 기능이긴 하나, Container 재사용 옵션을 제공한다. (Fast 원칙!)
Requirements
- 개발자 홈 디렉터리의
.testcontainers.properties
파일 설정testcontainers.reuse.enable=true
- JDBC url 설정에서
TC_REUSABLE=true
옵션 지정jdbc:tc:mysql:///test?TC_REUSABLE=true&TC_INITSCRIPT=file:src/test/resources/schema.ddl
위 2가지 설정이 되어 있어야, 컨테이너 재사용이 가능하다.