BackEnd/TDD

SpringBoot에 Test Container적용 (MySQL, Redis)

샤아이인 2022. 12. 13.

 

도커와 테스트 (TestContainers)

테스트를 위해서는 운영과 동일한 형태의 개발 환경에서 테스트 하는 것이 중요하다.

하지만 매번 동일하게 환경을 구축할 수 없고 모든 개발 자들과 같은 환경을 맞추기도 쉽지 않다.

이를 Test Container를 통해 해결해 보자!

 

(단점 테스트 수행 속도가 느려짐 → 다행이 이는 컨테이너를 한번만 인스턴스화 하는 방식으로 해결가능)

 

(중요) Docker는 켜져 있어야 합니다!!!

 

Docker Container를 활용한 일회용 인스턴스를 제공하는 JUnit 테스트 라이브러리를 말합니다.

 

JUnit 5 Quickstart

 

JUnit 5 Quickstart - Testcontainers for Java

JUnit 5 Quickstart It's easy to add Testcontainers to your project - let's walk through a quick example to see how. Let's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it. In our imaginary program, there

www.testcontainers.org

 

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가 모두 나와 로그를 알아보기 힘들다는 단점이 있기 때문입니다.

 

다음 원문을 살펴보면

 

Recommended logback configuration - Testcontainers for Java

Recommended logback configuration Testcontainers, and many of the libraries it uses, utilize SLF4J for logging. In order to see logs from Testcontainers, your project should include an SLF4J implementation (Logback is recommended). The following example lo

www.testcontainers.org

위 문장과 같이 DEBUG를 피하라고 권장하고 있습니다.

 

3. TestConatiner 생성 (수동으로 컨테이너 만들어주는 방식)

원래는 테스트 테스트메서드 하나당, 하나의 테스트 컨테이너를 인스턴스로 만들어 사용한후 컨테이너가 종료됩니다.

하지만 이러한 방식은 테스트 메서드가 class에 많으면 매번 컨테이너를 생성했다 종료하게 되니, 시간이 많이 걸리고 리소스 사용 측면에서도 낭비가 많이 되는 작업 입니다.

 

따라서 하나의 컨테이너를 인스턴스 화 한 후, 모든 테스트가 종료될때까지 재사용 하도록 작성해야 합니다.

크게 2가지 방식이 있는데,

  1. Singleton 으로 만드는 방식
  2. Static 키워드로 할당하는 방식

저는 2번 방식으로 사용하겠습니다. 공식 문서에서도 명시하고 있는 방식입니다.

https://www.testcontainers.org/test_framework_integration/manual_lifecycle_control/

 

하지만 static 키워드로 할당을 해도 2번이나 생성되는 문제가 있었는데, 이는 처음 properties를 가지고 한번 테스트 커넥션을 만들기 때문에 이때 생성된 컨테이너를 계속 사용하는 방식을 적용하면 컨테이너를 단 1번만 인스턴스화 하여 수행할수가 있게됩니다.

 

각종 테스트 설정 case마다 시간을 측정하고, 정리를 잘한 코드스쿼드의 Jay의 글을 소개합니다~~

TestContainer 재사용하기(feat. 테스트 실행시간 최적화)

 

TestContainer 재사용하기(feat. 테스트 실행시간 최적화)

0. 계기 Sikdorak 프로젝트를 진행하며 팀원들과 테스트 DB에 대한 고민을 했었는데요.(ku-kim,Seokho-Ham) 토론한 결과 실행환경과 비슷한 조건에서 테스트하기위해 TestContaienr 를 도입하기로 결정했습

jwkim96.tistory.com

(정리를 너무 잘해주셔서 저는 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를 위와 같이 생성한후, 사용하는 곳에서 상속하면 적용 가능합니다.

 

댓글