用語集 · CI/CD と DevOps
Testcontainers
別名: Testcontainers for Java, testcontainers-node, テストコンテナ, テストコンテナーズ
定義
データベース、メッセージブローカー、ブラウザーなどの本物の依存関係を、テストコードから使い捨ての Docker コンテナとして起動し、終了後に削除するライブラリです。
Testcontainers は、Java、Node.js、.NET、Go、Python などの言語で使えるオープンソースのライブラリで、テストの中から Docker を操作します。テストが「Postgres 16 のデータベース」を要求すると、ライブラリがコンテナを起動し、準備ができるまで待ち、接続情報を渡し、テストが終わるとコンテナを削除します。インメモリの代用品や共有環境ではなく、本物のデータベースエンジンに対してテストできます。
例(Testcontainers for Java 1.x、JUnit 5)
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Testcontainers
class DatabaseIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
@Test
void canQueryTheDatabase() throws Exception {
try (Connection conn = DriverManager.getConnection(
postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword());
ResultSet rs = conn.createStatement().executeQuery("SELECT 1")) {
assertTrue(rs.next());
}
}
}
static フィールドにすると、クラス内のすべてのテストで 1 つのコンテナを共有します。インスタンスフィールドにすると、テストごとに新しいコンテナが起動します。実際のスイートでは、リポジトリや Spring のコンテキストを getJdbcUrl() に向け、先にマイグレーションを実行します。
なぜ重要なのか
H2 や SQLite に対する結合テストは合格しても、本番のデータベースでは同じ SQL が拒否されることがよくあります。方言、制約、トランザクションの挙動が異なるからです。Testcontainers を使えば、モックに近い手軽さで本物のエンジンを使えます。さらに毎回クリーンな状態から始まるため、共有データに起因するフレーキーさもなくなります。
知っておくべきこと
- マシン上と CI ランナー上に Docker 互換のランタイムが必要です。GitHub がホストする Ubuntu ランナーでは Docker を利用できます。
- コンテナの起動には数秒かかります。コンテナはクラス単位またはスイート単位で共有し、テスト間ではコンテナを再起動するのではなくデータをリセットしましょう。
- イメージのバージョンは本番に合わせて固定しましょう。
- よく使われるサービス(Postgres、MySQL、Kafka、LocalStack、Selenium など)にはすぐに使えるモジュールがあり、それ以外のイメージは
GenericContainerで扱えます。 - テストダブルを置き換えるものではなく、補完するものです。ユニットテストは引き続きインメモリで高速に保つべきです。