Testcontainers について紹介

Testcontainers は、テストのために Docker コンテナを実行する Java ライブラリである。

概要

テスト環境は、プロジェクト設定の中でも特に重要な部分の 1 つである。テストでは、本番環境と同じ形のテスト環境で実行することが重要だからである。
しかし、毎回同じ環境を構築することはできず、すべての開発者と同じ環境を揃えることも簡単ではない。

そのためには、テストコードを実行するたびに Docker を使ってテスト用コンテナを起動し、テストを実行し、終了後にコンテナを停止して削除できるとよい。 Testcontainers を使うと、そのような機能を実現できる。

Testcontainers とは

Testcontainers は、テストのために Docker コンテナを実行する Java ライブラリである。
Docker を活用してインフラを構成でき、非常に多様なサービスをサポートしている。 FIRST 原則のうち、Repeatable 原則を守る助けにもなる。

Testcontainers 関連サイト

Testcontainer 構成

Testcontainers はなぜ使うべきか

テスト環境は、プロジェクト設定の中でも特に重要な部分の 1 つである。

もっとも難しく面倒な作業でもあるが、最初に一度きちんと整えておけば、その後テストを書くときに環境を気にせず、かなりきれいなテストコードを書けるようになる。一方で、プロジェクト環境設定の中で最も多くの時間を使い、多くの試行錯誤を経験する区間の 1 つともいえる。

Testcontainers は、MySQL、PostgreSQL、Redis、Apache Kafka、Amazon SQS などのシステムを Docker コンテナとして作成し、テストできる環境を提供する。

Testcontainers がなければ、実際のシステム環境を用意し、テストを行うたびにテストデータを初期化する過程が必要になる。 また、テスト用に作ったシステム環境へ他の人が同時にアクセスすると、別のテストの影響でテストが失敗することがある。あるいは外部モジュールの影響で、テストが断続的に失敗することもある。

つまり、状況や外部の影響によって結果が変わる可能性があるということだ。この場合、失敗箇所を見つけるのが非常に難しい。このような状態は冪等性が維持されていないと言われることがあり、テストでは非常に重要な概念である。冪等性を守ることが、テスト全体の生産性に大きな影響を与えるためである。

さまざまな Module のサポート

Testcontainers はさまざまなモジュールをサポートしている。いくつか挙げると次のとおりである。

Testcontainers を使う

ここでは簡単な使い方を説明する。

ライブラリの追加

Gradle で JUnit 5 と一緒に使うには、次のライブラリが必要である。

testImplementation "org.junit.jupiter:junit-jupiter:5.8.1"
testImplementation "org.testcontainers:testcontainers:1.17.3"
testImplementation "org.testcontainers:junit-jupiter:1.17.3"

テストコードの作成

@Testcontainers
public class RedisBackedCacheIntTest {

    private RedisBackedCache underTest;

    // container {
    @Container
    public GenericContainer redis = new GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
        .withExposedPorts(6379);

    // }

    @BeforeEach
    public void setUp() {
        String address = redis.getHost();
        Integer port = redis.getFirstMappedPort();

        // Now we have an address and port for Redis, no matter where it is running
        underTest = new RedisBackedCache(address, port);
    }

    @Test
    public void testSimplePutAndGet() {
        underTest.put("test", "example");

        String retrieved = underTest.get("test");
        assertThat(retrieved).isEqualTo("example");
    }
}

@Testcontainers

テスト中に Redis コンテナを実行するには、まず @Testcontainers をテストクラスに指定する必要がある。

@Testcontainers
public class RedisBackedCacheIntTest {

コンテナの作成

@Container
public GenericContainer redis = new GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
    .withExposedPorts(6379);

@Container アノテーションは、テストライフサイクルのさまざまなイベントをこのフィールドに通知するよう JUnit に指定する。 ここでは Testcontainers の GenericContainer を使い、Docker Hub の特定の Redis イメージを使用するよう設定し、指定したポート 6379 を公開するよう設定している。

コードがコンテナと通信できるか確認する

コンテナのアドレスを Testcontainers に問い合わせる。

String address = redis.getHost();

コンテナのポートを Testcontainers に問い合わせる。

Integer port = redis.getFirstMappedPort();

Container 再利用 (option)

Testcontainers を使うと、テストを実行するたびに Docker コンテナが毎回新しく作成されるため、時間がかかる。

これを解決するため、Testcontainers ではまだ公開サポートされていない機能ではあるが、Container 再利用オプションを提供している。Fast 原則に関係する機能である。

Requirements:

  1. 開発者のホームディレクトリにある .testcontainers.properties ファイルを設定する。
    testcontainers.reuse.enable=true
    
  2. JDBC URL 設定で TC_REUSABLE=true オプションを指定する。
    jdbc:tc:mysql:///test?TC_REUSABLE=true&TC_INITSCRIPT=file:src/test/resources/schema.ddl
    

上記 2 つの設定が行われている場合に、コンテナの再利用が可能になる。