[Spring Framework, Testcontainers, MyBatis] Testcontainers를 활용한 MyBatis Mapper 쿼리 검증 통합 테스트

mrcocoball·2025년 4월 22일

Spring Framework

목록 보기
18/20

개요

쿼리 관련 휴먼 에러

회사에서 MyBatis를 사용하고 있는 프로젝트에서 종종 쿼리 오류나 컬럼 매핑 실수와 같은 휴먼 에러 등으로 에러가 발생하는 경우가 종종 있었는데, 컴파일 단계에서 테이블의 컬럼과 객체의 필드 간 매핑 오류를 잡는 QueryDSL을 사용하지 않는다면 MyBatis와 같은 SQL 매퍼, 또는 JPA에서 JPQL을 사용하는 경우처럼 쿼리문을 문자열로 사용하는 경우 대부분 런타임에서 휴먼 에러를 발견하게 된다.

이러한 문제가 종종 있어왔는데, 최근 개발 관련 오픈채팅에서 참고하기 좋은 내용을 들었다.

통합 테스트를 통해 휴먼 에러를 조기에 잡아낸다

오픈채팅에서 들은 내용은 쿼리 관련 휴먼 에러가 런타임에서 메서드를 호출할 때 잡힌다면, 아예 배포되기 전 테스트 과정에서 검사를 해보면 된다는 것. 결과 검증을 하지 않고 단순 호출만 해서 런타임 예외가 발생하는지를 테스트한다는 것이다.

사실 이전까지 테스트 코드를 짰을 때 DB 통합 테스트는 간단한 CRUD 여부만 확인하고 넘어가는 경우가 많았는데 왜 이런 생각을 못했을까 싶었다. 물론 리포지토리 또는 매퍼의 메서드가 많으면 많을수록 테스트 코드의 길이는 길어지겠지만, 적어도 개발자가 인지하지 못하는 쿼리 관련 휴먼 에러를 조기에 잡아낼 수 있다는 점은 상당히 매력적으로 다가왔다.

더욱이, MyBatis와 같이 쿼리를 직접 사용하는 경우라면 기능의 추가 / 변경에 따른 쿼리 수정 시 더더욱 휴먼 에러 발생 확률이 높아지기에 이러한 통합 테스트를 짤 경우 리팩토링 내성도 생길 것은 자명했다.

Testcontainers와 MyBatis

그래서 이전 테스트 코드에는 DB 통합 테스트를 하지 않았었는데 바로 필요성을 느끼고 Testcontainers를 사용하기로 결정하였다.

그러나 여기서 중요한 이슈가 있었는데, 이전에 Testcontainers를 사용했을 때엔 Spring Data JPA, QueryDSL 기반 프로젝트였기 때문에 auto-ddl 기능을 사용하여 테스트 실행 시 필요한 스키마를 자동으로 생성하고 초기화해주었지만, MyBatis에는 그러한 기능이 없었다.

다행히 레퍼런스를 찾아보니 스키마를 생성할 SQL 스크립트를 작성하고, Spring 프레임워크에서 제공하는 ScriptUtils를 활용하여 테스트 실행 시 스키마를 생성하는 작업을 하면 되는 것으로 확인되어 처리를 진행하였다.

설정

의존성 추가

build.gradle 또는 pom.xml에 다음 의존성을 추가해준다. DB는 MySQL을 사용하였다.

// build.gradle

testImplementation("org.testcontainers:mysql:1.20.6")
testImplementation("org.testcontainers:junit-jupiter:1.20.6")
// pom.xml

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mysql</artifactId>
    <version>1.20.6</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.20.6</version>
    <scope>test</scope>
</dependency>

application.yml

application.yml에는 드라이버 클래스를 지정해주고 url을 지정해준다.
이 때 형식은 jdbc:tc:${DB이미지이름:태그}:///${DB 이름}?TC_INITSCRIPT=${SQL 스크립트 경로} 으로 해준다.

spring:
  datasource:
    driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
    # jdbc:tc:${DB이미지이름:태그}:///${DB 이름}?TC_INITSCRIPT=${SQL 스크립트 경로}
    url: jdbc:tc:mysql:8.0.36:///mappertest?TC_INITSCRIPT=static/TEST_ERD.sql

통합 테스트 클래스

@SpringBootTest
class MapperIntegrationTest {

    @Autowired
    private Mapper1 mapper1;
    @Autowired
    private Mapper2 mapper2;

    static MySQLContainer<?> mySQLContainer = new MySQLContainer<>("mysql:8.0.36") // DB이미지이름:태그
            .withDatabaseName("mappertest") // 테스트에 사용할 DB 이름
            .withUsername("test")
            .withPassword("test");

	// 테스트 실행 전 설정
    @BeforeAll
    static void setUp() {
        mySQLContainer.start();
    }

	// 테스트 종료 후 설정
    @AfterAll
    static void tearDown() {
        mySQLContainer.stop();
    }

    @Test
    @Transactional
    void 쿼리관련_오류_체크용_통합_테스트() {
    	// 매퍼 내에 있는 메서드 직접 호출 (결과 검증 X)
        mapper1.method1();
        mapper1.method2();
        mapper2.method1();
    }
    
}

유의해야 할 점

아무래도 auto-ddl을 사용하지 않다보니, 스키마가 변경될 경우 스키마 변경에 따른 마이그레이션 스크립트 등을 관리할 때 테스트 코드가 참조할 스크립트 역시 변경해줘야 하는 부분이 있다. 아주 심각한 문제는 아니긴 하지만 관리 지점이 하나 늘어났다는 점에서 좀 귀찮게 느껴지긴 하다.

Reference

https://velog.io/@iknow/testcontainers-%EC%A0%81%EC%9A%A9
https://testcontainers.com/guides/getting-started-with-testcontainers-for-java/

profile
Backend Developer

0개의 댓글