도커와 테스트 (TestContainers)
테스트를 위해서는 운영과 동일한 형태의 개발 환경에서 테스트 하는 것이 중요하다.
하지만 매번 동일하게 환경을 구축할 수 없고 모든 개발 자들과 같은 환경을 맞추기도 쉽지 않다.
이를 Test Container를 통해 해결해 보자!
(단점 테스트 수행 속도가 느려짐 → 다행이 이는 컨테이너를 한번만 인스턴스화 하는 방식으로 해결가능)
(중요) Docker는 켜져 있어야 합니다!!!
Docker Container를 활용한 일회용 인스턴스를 제공하는 JUnit 테스트 라이브러리를 말합니다.
1. 의존성 추가
testImplementation "org.junit.jupiter:junit-jupiter:5.8.1"
testImplementation "org.testcontainers:testcontainers:1.17.6"
testImplementation "org.testcontainers:junit-jupiter:1.17.6"
testImplementation "org.testcontainers:mysql:1.17.6" // mysql 컨테이너를 사용한다면 추가
gradle에 필요한 의존성을 추가해 줍시다~~
2. Logback 권장 설정
공식 문서에선 테스트 환경 test/resources 에 logback-test.xml 파일을 만들어 줄 것을 권장하고 있습니다.
다음 xml 파일을 살펴봅시다.
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.testcontainers" level="INFO"/>
<logger name="com.github.dockerjava" level="WARN"/>
</configuration>
하나 눈여겨 볼 점이 level="INFO" 라는 점 입니다.
DEBUG로 설정하게 될 경우, 수 백 줄의 DEBUG가 모두 나와 로그를 알아보기 힘들다는 단점이 있기 때문입니다.
다음 원문을 살펴보면
위 문장과 같이 DEBUG를 피하라고 권장하고 있습니다.
3. TestConatiner 생성 (수동으로 컨테이너 만들어주는 방식)
원래는 테스트 테스트메서드 하나당, 하나의 테스트 컨테이너를 인스턴스로 만들어 사용한후 컨테이너가 종료됩니다.
하지만 이러한 방식은 테스트 메서드가 class에 많으면 매번 컨테이너를 생성했다 종료하게 되니, 시간이 많이 걸리고 리소스 사용 측면에서도 낭비가 많이 되는 작업 입니다.
따라서 하나의 컨테이너를 인스턴스 화 한 후, 모든 테스트가 종료될때까지 재사용 하도록 작성해야 합니다.
크게 2가지 방식이 있는데,
- Singleton 으로 만드는 방식
- Static 키워드로 할당하는 방식
저는 2번 방식으로 사용하겠습니다. 공식 문서에서도 명시하고 있는 방식입니다.
https://www.testcontainers.org/test_framework_integration/manual_lifecycle_control/
하지만 static 키워드로 할당을 해도 2번이나 생성되는 문제가 있었는데, 이는 처음 properties를 가지고 한번 테스트 커넥션을 만들기 때문에 이때 생성된 컨테이너를 계속 사용하는 방식을 적용하면 컨테이너를 단 1번만 인스턴스화 하여 수행할수가 있게됩니다.
각종 테스트 설정 case마다 시간을 측정하고, 정리를 잘한 코드스쿼드의 Jay의 글을 소개합니다~~
TestContainer 재사용하기(feat. 테스트 실행시간 최적화)
(정리를 너무 잘해주셔서 저는 pass~~)
4. 알고 보니 직접 만드는 방식은 엄청난 삽질이였다…
JDBC를 사용하는경우 그냥 URL만 수정해주면 된다.
공식 홈페이지에서도 JDBC를 사용하는 경우 URL을 수정함으로써 별다른 수정없이 사용할수 있다고 위에 명시해주고 있다.
spring:
datasource:
url: jdbc:tc:mysql:8:///soolsul?TC_REUSABLE=true
username: test
password: test121212
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
url에서 :tc: 를 추가해주고, driver-class-name만 org.testcontainer.jdbc.ContainerDatabaseDriver로 변경해주면 그냥 끝난다!
와... 거의 신의 경지인것 같다… 이게 된다고?
testcontainer 의 DriverClass를 사용하면 URL 에 명시한 DB 종류에 따라 알아서 설정하고 동작하게 됩니다.
공식문서에서는 이렇게 프로토콜을 입력하면, hostname, port, database name 이 무시된다고 합니다.
알아서 설정해서 datasource 를 만들어 주는 것 같습니다. 마치 Sprgin Data JPA 과 같은 느낌인것 같습니다.
여기서 Datasource 생성 후 검증을 위해 DB 와 연결을 시도하는데, 이때 1회 mysql 컨테이너가 실행됩니다.
이걸 계속 사용하면 되는것 입니다.
5. Redis의 경우
Redis는 아쉽게도 JDBC의 기반이 아니라, 따로 만들어줘야 합니다...
public abstract class TestRedisContainer {
private static final String DOCKER_REDIS_IMAGE = "redis:7.0.5-alpine";
public static GenericContainer REDIS_CONTAINER =
new GenericContainer<>(DockerImageName.parse(DOCKER_REDIS_IMAGE))
.withExposedPorts(6379)
.withReuse(true);
static {
REDIS_CONTAINER.start();
}
@DynamicPropertySource
public static void overrideProps(DynamicPropertyRegistry registry) {
registry.add("spring.redis.host", REDIS_CONTAINER::getHost);
registry.add("spring.redis.port", () -> REDIS_CONTAINER.getMappedPort(6379).toString());
}
}
TestContainer같은 에너테이션을 추가하지 않아도 됩니다.
Container 에너테이션은 테스트마다 별도의 Redis를 작동시키기 때문에 전역으로 하나만 사용한다는 취지에 적합하지 않습니다.
따라서 전역으로 사용할 TestRedisContainer를 위와 같이 생성한후, 사용하는 곳에서 상속하면 적용 가능합니다.
'BackEnd > TDD' 카테고리의 다른 글
[RestAssured] Multiparts 테스트 작성시 Json데이터와 File을 함께 보내기 (Cannot serialize because cannot determine how to serialize content-type multipart/form-data) (0) | 2023.02.28 |
---|---|
[TDD] 테스트를 위한 생성자, 메서드 피하는 방법 (2) | 2022.07.26 |
[TDD] JUnit 만들기 - 2 (2) | 2022.07.15 |
[TDD] JUnit 만들기 - 1 (2) | 2022.07.15 |
[junit5] MockMvc에서 NestedServletException 통과시키기 (0) | 2022.03.11 |
댓글