본문 바로가기
DEV/Spring

Oracle DB TestContainers 통합 테스트 구현

by 화트마 2023. 10. 25.

 

배경

테스트 컨테이너는 도커 컨테이너를 활용하여 격리된 테스트 환경을 쉽게 구축할 수 있도록 해줍니다.

 

컨테이너를 활용한 격리된 테스트 환경의 장점은 다음과 같습니다. (chatGPT 참고)

  1. 일관된 테스트 환경 제공: 테스트 컨테이너는 테스트 환경을 컨테이너화하므로, 모든 테스트가 동일한 환경에서 실행됩니다. 이로써 테스트 결과가 일관되며, 테스트 간의 상호 작용 문제를 줄일 수 있습니다.
  2. 테스트 격리: 테스트 컨테이너는 테스트 간의 격리를 제공합니다. 각 테스트는 독립적인 컨테이너 내에서 실행되므로 다른 테스트에 영향을 미치지 않습니다.
  3. 복잡한 의존성 관리: 테스트 시스템이 복잡한 의존성을 가질 때, 테스트 컨테이너를 사용하면 이러한 의존성을 쉽게 관리할 수 있습니다. 데이터베이스, 메시지 큐, 외부 API 등과 같은 의존성을 실제 컨테이너로 대체할 수 있습니다.
  4. 빠른 설정 및 청소: 테스트 컨테이너는 필요한 설정을 프로그래밍적으로 정의하고, 테스트가 완료된 후에는 자동으로 청소됩니다. 이로 인해 테스트 환경을 설정하고 청소하는 데 드는 시간을 크게 절약할 수 있습니다.
  5. CI/CD 통합: 테스트 컨테이너는 CI/CD 파이프라인에서 쉽게 통합될 수 있습니다. 테스트 환경을 코드로 정의하고 버전 관리할 수 있으므로, 다양한 환경에서 일관된 테스트를 실행할 수 있습니다.

이러한 장점을 통해 실제 운영환경과 유사하나 외부로부터 격리되어 안정적인 통합 테스트 코드를 쉽게 개발할 수 있습니다.

 

여러명이 공유하는 DB를 사용한 테스트의 경우 데이터 격리가 되지 않기 때문에 멱등성이 보장되지 않아 테스트 결과가 비결정적입니다.

이는 언제든 테스트가 깨질 수 있음을 뜻합니다.

그리고 테스트가 깨지지 않게 하기 위해 테스트 데이터에 대한 관리에 불필요한 리소스가 들게 됩니다.

 

테스트 컨테이너의 단점으로는, 테스트 컨테이너를 올리는 시간만큼 테스트 빌드가 지연되는 점이 있습니다.

특히, 테스트 마다 컨테이너를 올릴 경우 (컨테이너 올리는 시간) * (테스트수) 만큼 지연됩니다.

따라서 전체 테스트에 하나의 컨테이너만 사용하도록 설정을 구현해보았습니다.

필요에 따라 추가로 컨테이너를 올리는 것도 가능합니다.

 

또한 통합 테스트의 경우 로컬/개발 환경에서만 실행되도록 설정을 추가하였습니다.

 


도커 컨테이너 사용을 위한 환경 설정

MAC M1 기준

 

1. Docker 설치

% brew install docker
% docker ps
정상수행되어야함

 

2. Colima 설치

💡 Docker Desktop의 이용약관을 보면 대기업에서는 이제 무료로 사용할 수 없게 되었습니다. 그리고 Intel 칩에서만 가동되었던 오라클 컨테이너의 경우 M1에는 돌아가지 않는 문제점도 있습니다. 그래서 Intel과 M1을 둘다 지원해주는 Colima를 설치해야합니다.
% brew install colima

 

3. Colima 실행

% colima start --memory 4 --arch x86_64
INFO[0000] starting colima                              
INFO[0000] runtime: docker                              
INFO[0000] preparing network ...                         context=vm
INFO[0000] starting ...                                  context=vm
INFO[0065] provisioning ...                              context=docker
INFO[0065] starting ...                                  context=docker
INFO[0072] done

 

4. 환경 변수, 심볼릭 링크 생성

% export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock
% export DOCKER_HOST=unix://${HOME}/.colima/default/docker.sock
% export TESTCONTAINERS_RYUK_DISABLED=true
% sudo ln -s $HOME/.colima/docker.sock /var/run/docker.sock

 

5. docker context를 colima로 변경하기

% docker context ls
NAME            DESCRIPTION                               DOCKER ENDPOINT                                          ERROR
colima *        colima                                    unix:///Users/peterica.seo/.colima/default/docker.sock
default         Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
desktop-linux   Docker Desktop                            unix:///Users/peterica.seo/.docker/run/docker.sock

% docker context use colima
colima
Current context is now "colima"

참고 : Colima 종료

% colima stop

 


Spring Boot TestContainer 설정 (컨테이너 한번만 올리기)

💡 테스트마다 컨테이너를 올릴 경우 빌드 속도가 매우 느려질 수 있어, 전체 테스트에서 한번의 컨테이너만 올리는 기준으로 작성하였습니다.

 

pom.xml 의존성 추가 및 local, dev환경에서만 통합 테스트 수행하도록 설정

local, dev에서만 실행되기 위해서는 반드시 integration 패키지 내부에 테스트 코드를 작성해주셔야합니다.

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>oracle-xe</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
    <version>5.8.0</version>
</dependency>

...

<profiles>
    <profile>
        <id>default</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <spring.profiles.active>default</spring.profiles.active>
        </properties>
    </profile>
    <profile>
        <id>dev</id>
        <properties>
            <spring.profiles.active>dev</spring.profiles.active>
        </properties>
    </profile>
    <profile>
        <id>qa</id>
        <properties>
            <spring.profiles.active>qa</spring.profiles.active>
        </properties>
        <build>
            <plugins>
                <plugin>
               		<groupId>org.apache.maven.plugins</groupId>
               		<artifactId>maven-surefire-plugin</artifactId>
               		<version>3.0.0-M1</version>
               		<configuration>
               			<excludes>
               				<exclude>**/integration/*.java</exclude>
               			</excludes>
               		</configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
    <profile>
        <id>stg</id>
        <properties>
            <spring.profiles.active>stg</spring.profiles.active>
        </properties>
        <build>
            <plugins>
                <plugin>
               		<groupId>org.apache.maven.plugins</groupId>
               		<artifactId>maven-surefire-plugin</artifactId>
               		<version>3.0.0-M1</version>
               		<configuration>
               			<excludes>
               				<exclude>**/integration/*.java</exclude>
               			</excludes>
               		</configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <spring.profiles.active>prod</spring.profiles.active>
        </properties>
        <build>
            <plugins>
                <plugin>
               		<groupId>org.apache.maven.plugins</groupId>
               		<artifactId>maven-surefire-plugin</artifactId>
               		<version>3.0.0-M1</version>
               		<configuration>
               			<excludes>
               				<exclude>**/integration/*.java</exclude>
               			</excludes>
               		</configuration>
                </plugin>
            </plugins>
        </build>
    </profile>

 

 


오라클 컨테이너 등록 방식 두가지

1. OracleContainer 클래스 등록 방식

OracleContaingerConfig.java

@Slf4j
public abstract class OracleContainerConfig {

    static final OracleContainer oracleContainer;

    static {
        oracleContainer = new OracleContainer("gvenzl/oracle-xe:21-slim-faststart")
                    .withDatabaseName("databasename")
                    .withUsername("testUser")
                    .withPassword("testPassword")
                    .withInitScript("init-schema.sql")
                    .withLogConsumer(new Slf4jLogConsumer(log));
        oracleContainer.start();
    }

    @DynamicPropertySource
    public static void properties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.jdbc-url", oracleContainer::getJdbcUrl);
        registry.add("spring.datasource.username", oracleContainer::getUsername);
        registry.add("spring.datasource.password", oracleContainer::getPassword);
    }
}
  • static 으로 단 한번의 컨테이너만 생성하여 사용
    • 테스트 코드마다 새로운 컨테이너를 올려야 한다면, 그냥 new OracleContainer 후 start를 테스트 코드마다 작성하면 됨
  • 다양하게 커스텀 가능
    • 도커 이미지  gvenzl/oracle-xe:21-slim-faststart 사용
    • DB 초기화 스크립트 init-schema.sql
  • 어플리케이션 실행시점에 컨테이너 올라옴

 

application-test.yml

spring:
    datasource:
      driver-class-name: oracle.jdbc.driver.OracleDriver
      type: com.zaxxer.hikari.HikariDataSource
      hikari:
        minimum-idle: 2
        maximum-pool-size: 10
        connection-test-query: SELECT 1 FROM DUAL

 

SampleTest.java

OracleContaingerConfig.java를 상속받아서 사용

@ActiveProfiles("test")
@SpringBootTest
class SampleTest extends OracleContainerConfig {

    @Test
    void test() {
    
    }
}
  • 상속만 해도 어플리케이션 실행 시점에 컨테이너가 올라옴

 

2. DataSource 등록 방식

💡 주의!! : DataSource (application-test.yml)에서 테스트 컨테이너를 올릴 경우, OracleContainer를 추가로 올리게 되면 컨테이너를 두번 올리게 됩니다. 

 

application-test.yml

spring
    datasource:
      jdbc-url: jdbc:tc:oracle:21-slim-faststart:///databasename?TC_INITSCRIPT=file:src/test/resources/init-schema.sql
      username: testUser
      password: testPassword
      driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
      type: com.zaxxer.hikari.HikariDataSource
      hikari:
        minimum-idle: 2
        maximum-pool-size: 10
        connection-test-query: SELECT 1 FROM DUAL

 

SampleTest.java

@ActiveProfiles("test")
@SpringBootTest
class SampleTest {

    @Autowired
    private DataSource dataSource;
    private JdbcTemplate jdbcTemplate;

    @BeforeEach
    public void beforeEach() {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    void test() {
    
    }
}
  • 해당 데이터 소스 사용 시점에 컨테이너 올라옴
  • OracleContainer를 따로 등록할 필요 없음.

 


추가 개선 필요 사항

  1. 컨테이너 내부 쿼리 로그 출력 필요 
  2. 초기 스키마 파일 테이블 별로 분리해서 관리 (flyway)

추가 개선 사항에 대해서는 추후 반영 후 업데이트 예정입니다.

 


참고

https://java.testcontainers.org/

https://dev.gmarket.com/76

https://peterica.tistory.com/420

https://www.devkuma.com/docs/testcontainers/colima-macos-m1/

https://umbum.dev/1127

댓글