JDBC 지원 — 임시 데이터베이스를 컨테이너로 띄우기
JDBC 지원 — 임시 데이터베이스를 컨테이너로 띄우기
테스트에서 진짜 DB와 같은 동작을 확인하고 싶은데 개발자 머신마다 DB를 설치해서 관리하는 건 큰 부담이에요. Testcontainers의 JDBC 지원을 쓰면 JDBC URL만 살짝 고쳐서 임시 데이터베이스를 컨테이너로 띄울 수 있어요. 이 페이지에서는 두 가지 방식, 즉 JDBC URL을 바꾸는 방식과 @Rule/@ClassRule을 쓰는 방식을 살펴볼게요.
본문
임시 데이터베이스는 두 가지 방법 중 하나로 얻을 수 있어요.
- 특별히 수정된 JDBC URL 사용: JDBC URL 문자열을 아주 간단히 수정하면, 애플리케이션 코드는 수정할 필요 없이 일회용 대체 데이터베이스를 Testcontainers가 제공해 줘요.
- JUnit
@Rule/@ClassRule: 이 모드는 테스트 전에 컨테이너 안에서 데이터베이스를 시작하고 테스트 후에 내려줘요.
JDBC URL 스킴으로 컨테이너 DB 시작하기
Testcontainers와 알맞은 JDBC 드라이버가 클래스패스에 있기만 하면, 일반 JDBC 연결 URL을 수정해 애플리케이션이 시작될 때마다 새로운 컨테이너화된 DB 인스턴스를 얻을 수 있어요.
참고:
- 이게 동작하려면 TC가 런타임에 애플리케이션 클래스패스에 있어야 해요
- Spring Boot(버전
2.3.0이전)에서는 드라이버를 수동으로 지정해야 해요spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
원래 URL: jdbc:mysql://localhost:3306/databasename
다음처럼 jdbc: 뒤에 tc:를 넣어요. 호스트 이름, 포트, 데이터베이스 이름은 무시되므로 그대로 두거나 아무 값으로 바꿔도 돼요.
참고로, 앞으로는 ///(호스트 없는 URI)를 써서 host:port 쌍이 중요하지 않음을 강조할게요. Testcontainers 관점에서 jdbc:mysql:8.0.36://localhost:3306/databasename과 jdbc:mysql:8.0.36:///databasename은 같은 URI예요.
주의: JDBC URL 지원을 쓰고 있다면 컨테이너 인스턴스를 직접 만들 필요가 없어요 — Testcontainers가 자동으로 만들어 줘요.
JDBC URL 예시
ClickHouse 사용
jdbc:tc:clickhouse:18.10.3:///databasename
CockroachDB 사용
jdbc:tc:cockroach:v21.2.3:///databasename
CrateDB 사용
jdbc:tc:cratedb:5.2.3:///databasename
DB2 사용
jdbc:tc:db2:11.5.0.0a:///databasename
MariaDB 사용
jdbc:tc:mariadb:10.3.39:///databasename
MySQL 사용
jdbc:tc:mysql:8.0.36:///databasename
MSSQL Server 사용
jdbc:tc:sqlserver:2017-CU12:///databasename
OceanBase 사용
jdbc:tc:oceanbasece:4.2.1-lts:///databasename
Oracle 사용
jdbc:tc:oracle:21-slim-faststart:///databasename
PostGIS 사용
jdbc:tc:postgis:9.6-2.5:///databasename
PostgreSQL 사용
jdbc:tc:postgresql:9.6.8:///databasename
QuestDB 사용
jdbc:tc:questdb:6.5.3:///databasename
TimescaleDB 사용
jdbc:tc:timescaledb:2.1.0-pg13:///databasename
PGVector 사용
jdbc:tc:pgvector:pg16:///databasename
TiDB 사용
jdbc:tc:tidb:v6.1.0:///databasename
Timeplus 사용
jdbc:tc:timeplus:2.3.21:///databasename
Trino 사용
jdbc:tc:trino:352://localhost/memory/default
YugabyteDB 사용
jdbc:tc:yugabyte:2.14.4.0-b26:///databasename
클래스패스 init 스크립트 사용하기
데이터베이스 컨테이너가 시작된 뒤, 코드에 연결이 주어지기 전에 init 스크립트를 실행할 수 있어요. 스크립트는 클래스패스에 있어야 하고, 다음과 같이 참조해요.
jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql
이것은 DB 스키마 설정 등 고정된 스크립트가 있을 때 유용해요.
파일에서 init 스크립트 사용하기
init 스크립트 경로가 file:로 시작하면 파일에서 로드돼요(작업 디렉터리 기준이며, 보통 프로젝트 루트예요).
jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql
init 함수 사용하기
DB 설정에 고정 스크립트를 쓰는 대신, 직접 정의한 Java 함수를 호출하는 것이 유용할 수 있어요. 이는 데이터베이스 스키마 마이그레이션 도구를 트리거하기 위한 의도예요. 그러려면 URL에 TC_INITFUNCTION을 추가해 클래스 이름과 메서드의 전체 경로를 넘겨요.
jdbc:tc:mysql:8.0.36:///databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction
init 함수는 java.sql.Connection만 유일한 파라미터로 받는 public static 메서드여야 해요. 예를 들어 볼게요.
public class JDBCDriverTest {
public static void sampleInitFunction(Connection connection) throws SQLException {
// e.g. run schema setup or Flyway/liquibase/etc DB migrations here...
}
...
데몬 모드로 컨테이너 실행하기
기본적으로 데이터베이스 컨테이너는 마지막 연결이 닫히는 즉시 중지돼요. 하지만 컨테이너를 시작해 두고 명시적으로 중지하거나 JVM이 종료될 때까지 계속 실행해야 하는 경우가 있어요. 그러려면 URL에 TC_DAEMON 파라미터를 추가해요.
jdbc:tc:mysql:8.0.36:///databasename?TC_DAEMON=true
이 파라미터가 있으면 열린 연결이 없어도 데이터베이스 컨테이너가 계속 실행돼요.
tmpfs 옵션으로 컨테이너 실행하기
컨테이너는 데이터를 호스트 메모리에 저장하는 tmpfs 마운트를 가질 수 있어요. DB 테스트 속도를 높이고 싶을 때 유용한데, 컨테이너가 중지되면 데이터가 사라진다는 점을 알아 두세요.
이 옵션을 컨테이너에 넘기려면 URL에 TC_TMPFS 파라미터를 추가해요.
jdbc:tc:postgresql:9.6.8:///databasename?TC_TMPFS=/testtmpfs:rw
옵션이 여러 개 필요하면 쉼표로 구분해요 (예: TC_TMPFS=key:value,key1:value1&other_parameters=foo).
tmpfs 마운트에 대한 자세한 내용은 공식 Docker 문서를 참고하세요.
데이터베이스 컨테이너 객체
URL 지원을 쓸 수 없거나 컨테이너를 세밀하게 조정해야 한다면 직접 인스턴스화할 수 있어요.
테스트 클래스에 @Rule이나 @ClassRule을 추가해요. 예를 들어 볼게요.
public class SimpleMySQLTest {
@Rule
public MySQLContainer mysql = new MySQLContainer();
이제 테스트 코드(또는 적절한 setup 메서드)에서 이 데이터베이스에 연결하는 데 필요한 정보를 얻을 수 있어요.
mysql.getJdbcUrl()— 코드가 연결할 수 있는 JDBC URL을 제공해요mysql.getUsername()— 드라이버에 전달할 사용자 이름을 제공해요mysql.getPassword()— 드라이버에 전달할 비밀번호를 제공해요
@Rule을 쓰면 테스트 메서드마다 격리된 컨테이너를 받아요. @ClassRule을 쓰면 테스트 클래스의 모든 메서드에 대해 격리된 컨테이너 하나를 받아요.
예시/테스트:
더 알아보기
- Testcontainers for Java 소개 — 라이브러리 의존성과 전반적인 개념
- 컨테이너 기반 테스트 시작하기 — GenericContainer로 어떤 이미지든 띄우기
- 네트워킹과 컨테이너 통신