본문 바로가기
테스트

Flyway, TestContainer를 통한 독립된 테스트 DB 환경 구성

by 코딩공장공장장 2025. 2. 22.

DB 연동 테스트는 Repeatable하고 Independent한 테스트 환경을 유지하는 것이 중요하다.

테스트를 여러번 실행해도 같은 결과가 보장되기 위해서 db의 상태가 변하지 않고 늘 일관되게 유지되야한다.

 

이번 포스팅에서 Flyway, TestContainer를 이용하여 일관되고 독립된 테스트 db를 구축하는 방법에 대해 소개하려고 한다.

 

Flyway


flyway는 DB 마이그레이션(변경사항)을 형상관리하는 소프트웨어이다.

프로그래밍 코드를 형상관리하는 git과 같이 DB 마이그레이션 정보를 담은 sql 파일을 형상관리한다고 생각하면 된다.

 

flyway는 주로 형상관리 목적으로 사용되지만 테스트 DB 환경 구축을 위해 사용될 수도 있다.

반복 실행 가능한 R 파일을 통해 더미 데이터를 추가하여 DB 상태를 일관되게 유지시킬 수 있다.

이를 통해 모든 DB 연동 테스트에서 동일하고 일관된 DB를 보장 받을 수 있다

내가 flyway를 사용했던 주 목적이기도 하다.

 

flyway에 대한 간략한 설명을 하며 DB 상태를 일관되게 유지하기 위해 관리한 방식을 소개하겠다.

flyway는 V(Version)파일과 R(Repeatable)파일이 존재한다.

 

V파일은 마이그레이션 목적의 파일로 ddl문을 주로 작성하였다.

테스트 DB라고 하더라도 운영 DB와 환경이 같아야하기에 운영 DB의 변경사항을 지속적으로 추적해야한다.

V파일의 버전번호를 늘려가며 마이그레이션 사항을 반영할 수 있다.

 

V 파일을 작성할 때, 실패 상황 발생시 원복하기 쉬운 구조를 갖추는 것이 중요하다.

CREATE TABLE과 같은 sql을 작성하기 전에는 DROP TABLE 구문을 실행될 수 있도록 하는 것이다.

DDL 문은 트랜잭션이 없기에 V파일 수행 중 특정 sql에서 실패하더라도 이미 수행된 ddl문은 롤백되지 않는다.

flyway를 통한 실패 복구 방법중 가장 간단한 것이 flyway_schema_history  테이블에 실패 기록을 삭제하고 다시 실행하는 것인데 다시 실행 할 때, 이미 반영된 테이블이 존재하면 CREATE 문에서 에러가 발생하므로 DROP TABLE 이후 CREATE TABLE 문이 수행되게 하여 반복 실행하더라도 문제 없도록 만드는 것이 중요하다.

다만, 이는 테스트 DB 목적으로 사용하는 경우에만 가능하고 운영DB라면 기존 테이블을 삭제하면 안되기에 절대 해서는 안되는 행동이다.

혹시나 테스트 DB가 운영환경에 반영되는 것을 막기 위해 아래와 같은 방법을 사용하였다.

  • 스프링  프로필을 통해 로컬에서만 flyway가 동작하도록 설정했다.
  • SQL 작성시 반드시 테스트 DB 명 함께 작성

이는 R 파일에서도 마찬가지이다.

 

R파일은 테스트 시에 사용할 데이터를 집어넣는 insert 쿼리들로 작성하였다.

R파일은 버전 번호 없이 매번 수행을 보장한다.

물론 정말 매번 수행된다는 것이 아니라 파일에 변경사항이 생기면(체크섬 달라지면) 재실행하여 DB에 반영하도록 한다.
따라서 멱등하게 작성해야 한다.

나는 R 파일의 첫줄에 반드시 DELETE FROM TABLE TB_NAME; 을 작성하여 기존의 데이터를 삭제하고 새롭게 넣어주도록 하였다.

테스트 데이터를 추가할 때마다, DELETE를 통해 기존 데이터를 전체 삭제하고 재반영하므로 충돌을 방지할 수 있다.

참고로, R파일은 파일명 사전순으로 실행된다.

외래키 제약조건이 존재하는 경우 R파일의 실행순서를 제어해야하는 경우가 생길 수 있다.

나는 R__000, R__100과 R 파일의 정렬 순서를 제어하여 외래키 관계가 있을 때, 부모 테이블이 먼저 실행되고 자식 테이블이 이후에 수행되도록 하였다.

 

 

[V, R 파일 관리 규칙]

  • V파일은 DROP TABLE;  CREATE TABLE; 구조로 작성
    V파일에 작성되는 DDL문은 트랜잭션 적용이 불가하다.
    V파일 수행 중 특정 DDL에서 실패가 일어나 복구해야하는 상황에서 이전에 반영된 테이블을 직접 삭제해야줘야할 수 도 있다. 이런 상황을 방지하고자 CREATE TABLE을 사용하기 이전에는 DROP TABLE을 작성하여 멱등하게 실행될 수 있도록 하자.
    ※ 다만, 운영 DB마이그레이션 목적이라면 DROP TABLE은 절대 불가, 오직 테스트 DB 환경 구축에서만

  • SQL 작성시 TEST_DB 명 반드시 작성
    로컬 환경에만 flyway를 활성화하여 운영 DB에 반영되는 것을 막을 수 있지만, 
    이중 안정장치 목적으로 sql에 TEST_DB 명을 반드시 작성하여 추가 안정장치를 마련한다.

  • R파일은 DELETE FROM ; INSERT 구조로 작성
    R파일은 반복 수행 가능하고 기존 파일에 추가 작성하여 반영할 수 있다. 
    기존 데이터와 충돌을 피하고자 DELETE문 수행 이후 INSERT 작성하여 멱등하게 실행될 수 있도록 하자.
    이또한 테스트DB 환경 구축 목적에서만 사용해야하는 규칙이다.

  • 외래키 조건으로 인해 R파일에도 실행 순서의 제어가 필요하므로 R__000, R__100과 같은 파일명 규칙

 

위와 같은 관리 규칙으로 관리하기도 쉽고 테스트 DB 상태를 일관되게 유지할 수 있다.

허나, 문제는 테스트를 진행하며 DB 상태가 변경될 수 있다는 것이다.

이를 방지하기 위해서는 롤백을 보장해야한다.

내가 사용한 스프링 프레임워크는 @DataJpaTest를 통한 DB 연동 테스트에서 롤백을 보장해준다.

따라서 flyway를 통해 모든 테스트, 모든 개발자에게 동일한 상태의 DB 환경을 제공할 수 있고,

DataJpaTest를 통해 롤백을 보장함으로써 테스트 수행 이후 DB 상태가 변경되지 않는 일관된 상태를 유지할 수 있다.

 

TestContainer


TestContainer는 컨테이너화된 테스트 환경을 제공하여 로컬 DB 환경의 제약을 받지 않고 빠르게 DB를 실행할 수 있도록 도와준다.

각 테스트 마다 격리된 DB 인스턴스를 생성하여 완전한 독립을 이룰 수 있지만, 그렇게 수행한다면 DB 인스턴스를 매번 새롭게 만들기에 성능이 매우 떨어진다.

 

Flyway와 DataJpaTest를 통해 고립된 테스트 DB 환경을 이미 구축하였기에 TestContainer를 통해서는 DB 환경 구축만 하도록 하였다.

 

따라서 나는 아래와 같이 테스트 컨테이너를 설정하였다.

class MysqlTCExtension : Extension {
    companion object {
        var mysqlContainer: MySQLContainer<Nothing> =
            MySQLContainer<Nothing>("mysql:8.3.0")
                .apply {
                    this.withDatabaseName("DB_NAME")
                    this.withUsername("USER")
                    this.withPassword("PW")
                    this.withUrlParam("characterEncoding", "UTF-8")
                }

        init {
            mysqlContainer.start()
        }
    }
}

 

나의 DB 테스트 환경을 적용한 어노테이션 설정을 아래와 같다.

@DataJpaTest
@ExtendWith(value = [MysqlTCExtension::class])
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(value = [QueryDslConfiguration::class, MockOrmBeanConfig::class, TCDataSourceConfig::class])
@ActiveProfiles("orm-jpa-adapter-tc-test")
annotation class TcDBJpaTest

 

orm-jpa-adapter-tc-test.yml에 설정된 Flyway 설정을 통해 테스트 DB 환경을 일관되게 구축하였고

DataJpaTest를 통해 모든 DB 연동 테스트에서 롤백을 보장하여 데이터가 변경되지 않도록 하였다.

이를 통해 고립성을 지켰으므로 테스트 컨테이너를 매번 인스턴스화 하지 않고 모든 테스트에서 공유할 수 있도록 하였다.

반응형