GitHub ActionsでTestcontainersを使う|MySQL統合テストのCI設定

TestcontainersとGitHub ActionsでMySQL統合テストを実行するイメージ

Spring BootとTestcontainersで書いたMySQL統合テストは、GitHub Actionsでも実行できます。CIへ移すときのポイントは、テストプロセスからDockerを使えること、ローカルと同じMySQLイメージを指定すること、Mavenが対象のテストを実行することです。

この記事では、GitHub-hostedのUbuntu runner上でMavenを直接実行する構成を扱います。ローカルで動くテストを前提に、ワークフローの追加、Spring Boot側との設定の対応、失敗時に残すログを整理します。

目次

ローカルで通る統合テストをCIでも動かす理由

DBアクセスを変更したとき、Javaのコンパイルが成功しても、SQLやテーブル定義との対応が正しいとは限りません。MySQLを使う統合テストをPull Requestで実行すれば、保存・検索処理に影響する変更をマージ前に確認できます。

CIでは、リポジトリに含まれるコードと設定からテスト環境を組み立てます。手元のDBに残っているテーブルやデータへ依存せず、MySQLの起動、スキーマの初期化、テスト実行までを同じ手順にできることが利点です。ただし、本番と同じ照合順序やSQLモードまで自動的にそろうわけではありません。検証に必要なDB設定はテスト側で管理します。

Repositoryのテストや初期化SQLがまだない場合は、先にローカル構成を用意してください。次の記事では、この記事で引き継ぐTestMysqlとNoteRepositoryTest、接続設定の作り方を説明しています。

GitHub ActionsでTestcontainersを動かす前提

Testcontainersは、テストからDocker APIを通じてMySQLコンテナを作成します。Docker CLIが見つかるだけでは不十分で、Mavenから起動されるJavaプロセスがDockerデーモンへ接続できる必要があります。さらに、そのJavaプロセスからコンテナの公開ポートへ接続できることも必要です。

実行環境準備と確認のポイント
GitHub-hosted Ubuntu runner標準のVM環境にあるDockerを利用し、ジョブ内でデーモンへの接続を確認する。
self-hosted runner管理者がDockerの導入・起動、runner実行ユーザーの権限、ネットワーク、容量、後片付けを整える。
ジョブ自体をコンテナ内で実行Docker APIへの接続経路と、起動したMySQLへの接続経路を別々に設計する。本記事のワークフローとは前提が異なる。

本記事ではruns-on: ubuntu-24.04を指定し、ジョブにcontainer:を設定しません。OSラベルを指定しても、runner内のソフトウェア一式が固定されるわけではないため、環境差を調べるときはジョブログのrunnerイメージ情報も確認します。

また、GitHub Actionsのservices:でMySQLを起動する方式と、Testcontainers方式は別です。service container方式ではワークフローがDBを用意し、テストへ接続情報を渡します。Testcontainers方式ではテストコード側にコンテナ定義を置き、JUnitやSpringの管理に合わせて起動・停止します。

既存のMySQLContainerを使うテストをCIへ移す場合、同じ用途のMySQLをservicesにも追加する必要はありません。Testcontainersが用意した接続情報をそのまま使います。

self-hosted runnerでは、Dockerへアクセスできるジョブにホストを操作できる強い権限が伴います。外部からのPull Requestを受け入れる場合は、信頼できないコードを永続的な社内runnerで実行しないよう、実行対象と環境の分離を設計してください。

Mavenプロジェクトのワークフロー例

以下は、ルートにpom.xmlがある単一モジュールの例です。Java 17、Spring Boot 3.5の親POMによる依存・プラグイン管理、Testcontainers 1.xを前提とします。テストクラス名は、既存記事と同じNoteRepositoryTestです。

.github/workflows/mysql-integration-test.ymlへ次のワークフローを配置します。MYSQL_IMAGEのmysql:8.4は設定例なので、ローカルのテストで選定したイメージに合わせてください。8.4のような系列タグは参照先が更新されるため、再現性を高める場合は具体的なタグやダイジェストを管理します。

name: MySQL integration tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-24.04
    timeout-minutes: 20
    env:
      MYSQL_IMAGE: mysql:8.4

    steps:
      - name: Checkout source
        uses: actions/checkout@v7

      - name: Set up Java
        uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: '17'
          cache: maven

      - name: Check Docker and Maven
        run: |
          docker version
          docker info
          mvn --version

      - name: Run tests
        run: >-
          mvn -B --no-transfer-progress
          "-Dtest.mysql.image=${MYSQL_IMAGE}"
          test

      - name: Upload test reports
        if: ${{ always() }}
        uses: actions/upload-artifact@v7
        with:
          name: surefire-reports
          path: target/surefire-reports/
          if-no-files-found: warn
          retention-days: 7

Check Docker and MavenでDockerのサーバー情報を取得できれば、少なくともそのステップの実行ユーザーからデーモンへ接続できています。イメージの取得やMySQLの起動、Javaからの接続まで成功するかは、続くテストで確認します。

MYSQL_IMAGEはシェルの環境変数です。既存記事のTestMysql.create()はJVMのシステムプロパティtest.mysql.imageを読むため、Mavenの実行引数で値を渡しています。環境変数を定義するだけでは、このJavaコードの入力にはなりません。

cache: mavenはMavenの依存取得を助ける設定で、MySQLのDockerイメージをキャッシュするものではありません。また、20分のタイムアウトとレポートの7日間保持は運用例です。テスト件数や調査に必要な期間に合わせて調整します。

Maven Wrapperを管理しているプロジェクトなら、Mavenの確認と実行を./mvnw --version、./mvnw -B --no-transfer-progress ... testへ置き換えられます。必要なWrapperファイルをコミットし、Linux上の実行権限と改行コードも確認してください。

Actionsの例では各公式リポジトリで確認できる現行メジャータグを指定しています。運用時はcheckout、setup-java、upload-artifactの対応条件を確認し、依存を厳密に固定する方針なら確認したコミットSHAを指定します。

Spring BootとTestcontainers側の設定を確認する

CI専用のDataSourceを追加する前に、既存のテストがTestcontainersの接続情報を使っているか確認します。JUnit管理の構成なら、@Testcontainersと@Containerが起動・停止を担当し、@ServiceConnectionがSpring Bootへ接続詳細を渡します。Spring管理の構成を採用している場合は、その管理方法をCIでも引き継ぎます。

接続先をlocalhost:3306へ固定する必要はありません。Testcontainersが割り当てたホストとポートを利用し、独自DataSourceがある場合は、その接続先にもテスト用の設定が反映されるか確認してください。

次は、Spring Boot 3.5の親POMを利用するプロジェクトのpom.xmlへ組み込む断片です。既存のbuildや同じプラグイン定義がある場合は、その中へ統合します。Testcontainersの依存関係はローカル構成のものを使い、CI用に別の系列へ変更しません。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <failIfNoTests>true</failIfNoTests>
                <systemPropertyVariables>
                    <test.mysql.image>${test.mysql.image}</test.mysql.image>
                </systemPropertyVariables>
            </configuration>
        </plugin>
    </plugins>
</build>

この例では、ワークフローから渡された値をテストJVMへ明示的に引き継ぎ、テストが一件も検出されなければ失敗させます。Spring BootのBOMだけを利用している場合はプラグイン管理が別になるため、JUnit Jupiterを実行できるSurefireのバージョンもプロジェクト側で管理します。

MySQLを使う「統合テスト」であっても、Mavenが実行する段階はテスト名とプラグイン設定で決まります。 この例のNoteRepositoryTestはSurefireの既定の検出対象に入り、mvn testで実行する構成です。

NoteRepositoryITのような名前でFailsafeを使うプロジェクトでは、Failsafeのintegration-testとverifyのゴールをライフサイクルへ設定し、CIもmvn verifyに合わせます。必要なシステムプロパティの受け渡しと、レポートの保存先target/failsafe-reports/も変更します。コマンドをverifyへ変えるだけでは、未設定のFailsafeが自動で有効になるわけではありません。

スキーマの準備もローカルとそろえます。既存記事の構成ならsrc/test/resources/db/test-schema.sqlをコミットし、withInitScriptから読み込みます。FlywayやLiquibaseを利用している場合は、その初期化方針を使い、同じテーブルを複数の仕組みで作成しないようにしてください。

ワークフローのイベントやMavenの役割から確認したい場合は、次の入門記事が補足になります。

CIで失敗したときのログと切り分け

失敗したら、Actionsのジョブログで、どのステップまで進んだかを確認します。Mavenの最後の失敗メッセージだけでなく、その前に出たTestcontainersのログと例外の原因を追うと、調べる範囲を絞れます。

止まった段階・症状優先して確認する内容
Docker確認ステップが失敗デーモンの起動状態、実行ユーザーの権限、Dockerの接続先。self-hostedではサービス実行ユーザーの環境も確認する。
CLIは通るがTestcontainersがDockerへ接続できないJavaプロセスに渡る環境変数、Docker接続設定、TestcontainersとDockerの互換性。
イメージを取得できない指定したタグ、CPUアーキテクチャ、レジストリの認証・取得制限、DNSやプロキシ。
コンテナ起動・待機で失敗MySQLや補助コンテナのログ、メモリ・ディスク容量、起動オプション、ネットワーク。
DBへ接続できないTestcontainersが返す接続先の利用、固定URLの混入、独自DataSource、停止済みコンテナを参照していないか。
テーブルがない・初期化で失敗SQLファイルの配置と大文字小文字、マイグレーションの適用、初期化処理の重複。
期待値が一致しない実行したSQL、初期データ、文字コード・照合順序・タイムゾーン、共有DBへのテスト間干渉。
成功表示なのに対象テストが見当たらないSurefireとFailsafeの対象名、実行フェーズ、プロファイル、スキップ設定、レポートのクラス名と件数。

ワークフローのif: ${{ always() }}は、テスト失敗後にもレポートの保存ステップを実行するための指定です。Surefireのレポートで、対象クラスの実行件数、失敗、エラー、スキップを確認します。コンパイル前やDocker確認で止まった場合は、まだレポートが生成されていないこともあります。

failIfNoTestsは、目的のMySQLテストが実行されたことまで保証しません。他のテストだけが動いて成功する場合もあるため、初回導入時はNoteRepositoryTestの結果を個別に確認してください。Dockerが利用できないときにテストをスキップする設定も、必須のCIチェックに適しているか見直します。

MySQLの標準出力が必要なら、Testcontainersのログ取得やログ転送をテスト側へ組み込みます。テスト終了時にコンテナが削除されると、後続のシェルステップでdocker logsを実行しても取得できません。また、Surefireのレポートにはコンテナの全ログが自動で保存されるわけではありません。保存するログにパスワードや業務データが含まれないよう、出力内容も確認します。

起動失敗への対処として、待機時間を延ばしたり、後片付けを担うRyukを無効化したりする前に、失敗した対象と原因を確認してください。認証・権限・接続先の問題は時間を延ばしても解消せず、後片付けを止めるとself-hosted runnerにコンテナなどが残る原因になります。

ローカルとの差を調べる際は、コミット、JavaとMavenのバージョン、解決されたTestcontainersのバージョン、MySQLイメージ、実行コマンドをそろえます。CIだけで並列化している場合は、共有データへの同時更新と、複数コンテナによるリソース消費も確認しましょう。

まとめ

GitHub ActionsでTestcontainersのMySQL統合テストを動かすには、Dockerを利用できるrunner上で、ローカルと同じイメージ指定とMavenの実行設定を使います。テストコードがDBを起動する構成なら、ワークフローにMySQLのservice containerを重ねて用意する必要はありません。

  • GitHub-hosted Ubuntu runnerでMavenを直接実行し、最初にDockerへの接続を確認する。
  • MySQLイメージをテストJVMへ渡し、接続先はTestcontainersの情報を利用する。
  • Surefire・Failsafeの設定に実行コマンドを合わせ、対象クラスの実行結果を確認する。
  • 失敗時にもレポートを残し、Docker接続、イメージ取得、DB起動、SQL実行の順に原因を絞る。

設定を確認する際の公式資料は、GitHub Actionsのコンテナ化されたサービス、Spring Boot 3.5のTestcontainers連携、Maven Surefire Plugin、Maven Failsafe Pluginです。まず既存のMySQLテスト一つがCIで実行されることを確認し、その構成を基準に対象を広げていきましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次