Testcontainers for Java 시작하기
Testcontainers for Java 시작하기
이 가이드에서는 Testcontainers for Java를 이용해 실제 PostgreSQL 인스턴스로 Java 애플리케이션을 만들고 데이터베이스 상호작용을 테스트하는 방법을 배워요.
출처: 문서
본문
이 가이드를 통해 다음 내용을 배울 수 있어요.
- Maven으로 Java 프로젝트 만들기
- PostgreSQL에서 고객 레코드를 관리하는
CustomerService구현하기 - 실제 Postgres 데이터베이스로 Testcontainers를 사용해 통합 테스트 작성하기
- 테스트를 실행하고 모든 게 잘 동작하는지 확인하기
사전 준비 (Prerequisites)
- Java 17 이상
- Maven 또는 Gradle
- Testcontainers가 지원하는 Docker 환경
참고: Testcontainers가 처음이라면 Testcontainers 개요를 방문해 Testcontainers가 무엇이고 어떤 장점이 있는지 알아보는 걸 권장해요.
Java 프로젝트 만들기
선호하는 IDE에서 Maven으로 Java 프로젝트를 만들어요. 이 가이드는 Maven을 쓰지만, 선호한다면 Gradle을 써도 돼요.
pom.xml에 다음 의존성을 추가해요.
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
이렇게 하면 Postgres JDBC 드라이버, 로깅용 logback, 테스트용 JUnit 5, 그리고 JUnit 5 지원을 위한 최신 maven-surefire-plugin이 추가돼요.
비즈니스 로직 구현하기
Customer 레코드를 만들어요.
package com.testcontainers.demo;
public record Customer(Long id, String name) {}
JDBC 연결 매개변수를 보관하고 데이터베이스 Connection을 제공하는 DBConnectionProvider 클래스를 만들어요.
package com.testcontainers.demo;
import java.sql.Connection;
import java.sql.DriverManager;
class DBConnectionProvider {
private final String url;
private final String username;
private final String password;
public DBConnectionProvider(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
}
Connection getConnection() {
try {
return DriverManager.getConnection(url, username, password);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
CustomerService 클래스를 만들어요.
package com.testcontainers.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class CustomerService {
private final DBConnectionProvider connectionProvider;
public CustomerService(DBConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider;
createCustomersTableIfNotExists();
}
public void createCustomer(Customer customer) {
try (Connection conn = this.connectionProvider.getConnection()) {
PreparedStatement pstmt = conn.prepareStatement("insert into customers(id,name) values(?,?)");
pstmt.setLong(1, customer.id());
pstmt.setString(2, customer.name());
pstmt.execute();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public List<Customer> getAllCustomers() {
List<Customer> customers = new ArrayList<>();
try (Connection conn = this.connectionProvider.getConnection()) {
PreparedStatement pstmt = conn.prepareStatement("select id,name from customers");
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
long id = rs.getLong("id");
String name = rs.getString("name");
customers.add(new Customer(id, name));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return customers;
}
private void createCustomersTableIfNotExists() {
try (Connection conn = this.connectionProvider.getConnection()) {
PreparedStatement pstmt = conn.prepareStatement("""
create table if not exists customers (
id bigint not null,
name varchar not null,
primary key (id)
)
""");
pstmt.execute();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
CustomerService가 하는 일을 살펴보면,
- 생성자는
createCustomersTableIfNotExists()를 호출해서 테이블이 존재하도록 해요. createCustomer()는 데이터베이스에 고객 레코드를 삽입해요.getAllCustomers()는customers테이블의 모든 행을 가져와Customer객체 목록으로 반환해요.
Testcontainers로 테스트 작성하기
CustomerService 구현은 준비됐지만 테스트하려면 PostgreSQL 데이터베이스가 필요해요. Testcontainers를 사용하면 Docker 컨테이너에서 Postgres 데이터베이스를 띄우고 그 데이터베이스를 대상으로 테스트를 실행할 수 있어요.
Testcontainers 의존성 추가하기
pom.xml에 Testcontainers PostgreSQL 모듈을 테스트 의존성으로 추가해요.
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-postgresql</artifactId>
<version>2.0.4</version>
<scope>test</scope>
</dependency>
애플리케이션이 Postgres 데이터베이스를 사용하기 때문에, Testcontainers Postgres 모듈은 컨테이너 관리를 위한 PostgreSQLContainer 클래스를 제공해요.
테스트 작성하기
src/test/java 아래에 CustomerServiceTest.java를 만들어요.
package com.testcontainers.demo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.postgresql.PostgreSQLContainer;
class CustomerServiceTest {
static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:16-alpine");
CustomerService customerService;
@BeforeAll
static void beforeAll() {
postgres.start();
}
@AfterAll
static void afterAll() {
postgres.stop();
}
@BeforeEach
void setUp() {
DBConnectionProvider connectionProvider = new DBConnectionProvider(
postgres.getJdbcUrl(),
postgres.getUsername(),
postgres.getPassword()
);
customerService = new CustomerService(connectionProvider);
}
@Test
void shouldGetCustomers() {
customerService.createCustomer(new Customer(1L, "George"));
customerService.createCustomer(new Customer(2L, "John"));
List<Customer> customers = customerService.getAllCustomers();
assertEquals(2, customers.size());
}
}
테스트가 하는 일을 살펴보면,
postgres:16-alpineDocker 이미지로PostgreSQLContainer를 선언해요.@BeforeAll콜백은 어떤 테스트 메서드가 실행되기 전에 Postgres 컨테이너를 시작해요.@BeforeEach콜백은 컨테이너의 JDBC 연결 매개변수로DBConnectionProvider를 만들고, 그다음CustomerService를 만들어요.CustomerService생성자는customers테이블이 없으면 만들어줘요.shouldGetCustomers()는 고객 레코드 2개를 삽입하고 모든 고객을 가져와 그 개수를 단언해요.@AfterAll콜백은 모든 테스트 메서드가 끝난 뒤 컨테이너를 중지해요.
테스트 실행과 다음 단계
Maven으로 테스트를 실행해요.
$ mvn test
로그에서 Testcontainers가 Postgres Docker 이미지를 Docker Hub에서 가져오고(로컬에 없을 경우), 컨테이너를 시작하고, 테스트를 실행하는 걸 볼 수 있어요. Testcontainers로 통합 테스트를 작성하는 건 IDE에서 실행할 수 있는 유닛 테스트를 작성하는 것과 비슷해요. 팀원이 이 프로젝트를 클론해도 자신의 머신에 Postgres를 설치할 필요 없이 테스트를 실행할 수 있답니다.
요약 (Summary)
Testcontainers for Java 라이브러리는 목(mock) 대신 프로덕션에서 쓰는 것과 같은 종류의 데이터베이스(Postgres)를 사용해 통합 테스트를 작성할 수 있게 도와줘요. 목을 쓰지 않고 실제 서비스와 대화하기 때문에, 코드를 리팩터링해도 애플리케이션이 예상대로 동작하는지 검증할 수 있답니다.
Postgres 외에도 Testcontainers는 많은 SQL 데이터베이스, NoSQL 데이터베이스, 메시징 큐 등을 위한 전용 모듈을 제공해요. 테스트에 필요한 어떤 컨테이너화된 의존성이든 Testcontainers로 실행할 수 있어요.
Testcontainers에 대해 더 알아보고 싶다면 Testcontainers 개요를 방문해요.
더 읽어보기 (Further reading)
- JUnit 5를 이용한 Testcontainers 컨테이너 수명주기 관리
- 테스트를 위해 H2를 실제 데이터베이스로 교체하기
- Java Spring Boot 프로젝트에서 Testcontainers 시작하기