🧪

Spring Boot における結合テスト環境のカイゼン〜H2 から Testcontainers への移行〜

2024/12/02に公開

はじめに

テスト用データベースに H2 を使うのをやめて、Testcontainers で立てた DB コンテナを使うように変更したので、背景や Testcontainers の導入方法などについてご紹介します。

環境

  • Java 17
  • Spring Boot 3.3.5
  • spring-boot-testcontainers 3.3.5

なぜ Testcontainers に移行したか?

以前は以下のような構成で、ローカルとテストで異なる DB で開発をしていました。

  • ローカル
    • 本番環境と同じバージョンの MariaDB を Docker で立てて開発
  • テスト

H2 はインメモリデータベースとして導入が簡単で、これまで便利に使っていましたが、大きく2つの問題がありました。

  • テストの信頼性の問題
    • 本番環境と異なるデータベースのため、テストが実際の動作を正確に反映しない
  • 互換性の問題
    • H2 はある程度他のデータベースとの互換性を持つものの、完全ではありません。[1]

上記の内、移行のきっかけになったのは互換性の問題です。
ある機能の結合テストを書く際に、ローカル環境での動作確認用に使用していた SQL をそのまま @Sql で投入しようとしたところ、互換性の問題でエラーになる事案が発生しました。
以前から、テストの信頼性の問題は認識していましたが、何か問題が顕在化している訳でもなかったので、どこかで Testcontainers を導入しようかなぐらいに思ってたところ、互換性の問題が顕在化したので、これを機に Testcontainers に移行しました。

Testcontainers の導入

このあたりを参考に実装しました。
ざっくりと、やったことやポイントを解説します。

まず、必要なライブラリを追加しました。

dependencies {
    testImplementation 'org.springframework.boot:spring-boot-testcontainers'
    testImplementation 'org.testcontainers:mariadb'
    testImplementation 'org.mariadb.jdbc:mariadb-java-client'
}

次に、以下のようにしてコンテナを構成しました。[2]

public abstract class AbstractIntegrationTest { // ①
    static final MariaDBContainer<?> mariadb;

    @ServiceConnection // ②
    static {
        mariadb =
            new MariaDBContainer<>(DockerImageName.parse("mariadb:10.6.14"))
                .withDatabaseName("db")
                .withCopyFileToContainer(MountableFile.forClasspathResource("schema.sql"), "/docker-entrypoint-initdb.d/01-schema.sql")
                .withEnv("TZ", "Asia/Tokyo");
        mariadb.start();
  }
}

① では、AbstractIntegrationTest という結合テスト用の基底クラスを作成してコンテナを起動させています。
各結合テストは、AbstractIntegrationTest を継承して実装します。
このように書くことで、コンテナがシングルトンになり、一度起動したコンテナを使い回せるので、テストを高速に実行できます。
もし、テストごとにコンテナを起動する場合は、@Testcontainers@Container を使います。[3]

②では、@ServiceConnection アノテーションを使って、コンテナ立ち上げ時に動的に決まる DB の接続情報を spring.datasource.url などのプロパティに設定しています。
Spring Boot 3.0 までは @DynamicPropertySource を使って実装する必要がありましたが、3.1 から @ServiceConnection アノテーションを付与するだけで済むようになりました。[4]

その他、ポートは動的に決めるべきとか、ファイルの配置はマウントよりもコピーの方がいいなどのプラクティスも取り込みました。

おわりに

Testcontainers を使った Spring Boot における結合テスト環境のカイゼンについてご紹介しました。
導入する中で、「既存の SQL に H2 だと動くけど、MariaDB だと動かない」ものがあることに気づけたり、導入後、DB 以外のコンテナも Testcontainers で用意する動きが広がって自動テストで担保できる範囲が増えたので、導入してよかったと思います!

脚注
  1. モードによって特定の DB の機能の一部をエミュレートできます。
    https://www.h2database.com/html/features.html ↩︎

  2. 実行には環境変数が必要な場合があります。
    https://java.testcontainers.org/supported_docker_environment/ ↩︎

  3. https://java.testcontainers.org/test_framework_integration/junit_5/ ↩︎

  4. https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.1-Release-Notes#testcontainers ↩︎

BABY JOB  テックブログ

Discussion