Testcontainers とは何か、なぜ使うべきなのか?
コードを入手する
現代のソフトウェアシステムは、さまざまな技術やツールを活用して複雑なビジネス問題を解決しています。今日では、ほとんどのソフトウェアシステムが単独で動作することはなく、通常はデータベース、メッセージングシステム、キャッシュプロバイダー、その他の外部サービスと連携しています。また、現在の競争の激しい市場では、タイム・トゥ・マーケット(市場投入までの時間) が非常に重要です。企業は製品を可能な限り早く市場に投入し、フィードバックを得てそれを反映しながら改善していくことを求めています。このようなアジャイルな開発を実現するには、堅実な継続的インテグレーション(CI)と継続的デプロイメント(CD)プロセスが必要です。 CI/CDプロセスの重要な部分の1つが、自動化されたテストです。これにより、アプリケーションの挙動が正しいことを保証します。
ユニットテストは、データベースやメッセージングシステムなどの外部サービスから切り離して、ビジネスロジックや実装の詳細をテストするのに役立ちます。しかし、アプリケーションコードの大部分はこれらの外部サービスとの統合に関係している場合があります。アプリケーションに対して十分な信頼を持つためには、ユニットテストと共に統合テストも記述し、アプリケーションが完全に機能していることを確認する必要があります。
これまで、統合テストは「統合テスト環境」を維持する難しさから、困難であると考えられてきました。事前にプロビジョニングされたインフラストラクチャを使用した統合テストには、以下のような課題があります:
- テストを実行する前に、インフラストラクチャが正常に稼働していることを確認し、データが特定の望ましい状態に事前構成されている必要があります。
- 複数のビルドパイプラインが並行して実行される場合、1つのテスト実行が他のテストデータに干渉し、不安定なテスト(フレイキーテスト)やテストデータの汚染といった問題が発生する可能性があります
上記の課題のため、一部の人々は、統合テストで必要なサービスのインメモリ版や組み込み版を使用する方法に傾くことがあります。例えば、アプリケーションが Postgres データベースを使用している場合、テストでは H2 のインメモリデータベースを代替として使用するケースがあります。これは統合テストを書かないよりは改善されていますが、これらのサービスのモックやインメモリ版を使用することには独自の問題も伴います。
- インメモリサービスは、実際のプロダクションサービスのすべての機能を備えていない可能性があります。 例えば、アプリケーションで Postgres や Oracle データベースの高度な機能を使用している場合、H2 のようなインメモリデータベースではそれらのすべての機能がサポートされていない可能性があります。最悪の場合、開発者がこれらのシステムの強力な機能を採用することを慎重に考えるようになってしまうことさえあります。これは、対応する代替品でその機能を再現するのが困難なためです。
- インメモリサービスはフィードバックサイクルを遅らせる原因にもなります。 例えば、SQL クエリを作成し、H2 インメモリデータベースでテストした結果、問題なく動作しているとします。しかし、アプリケーションをデプロイした後に、そのクエリの構文が H2 では動作しても、実際のプロダクションデータベース(Postgres や Oracle)では動作しないことに気づく場合があります。また、この問題を軽減するために複数の異なる実装を維持しなければならないかもしれません。このようなテストは、変更に対するフィードバックサイクルを迅速に得るというテストの目的を効果的に損なうことになります。
さあ、Testcontainers の素晴らしい世界へようこそ! ここでは、実際のサービスを使用した統合テストが可能になるだけでなく、ユニットテストを書くのと同じくらい簡単に行えます 🙂
Testcontainers とは何か?
Testcontainers は、Docker コンテナでラップされた実際のサービスを使用して統合テストを簡単かつ軽量にセットアップするためのテストライブラリです。Testcontainers を使用することで、プロダクション環境で利用するのと同じタイプのサービスとやり取りをするテストを、モックやインメモリサービスを使わずに記述できます。
Testcontainers を使用した典型的な統合テストの流れは次のとおりです:
- テスト実行前:
- Testcontainers の API を使用して、必要なサービス(データベース、メッセージングシステムなど)の Docker コンテナを起動します。
- アプリケーションの設定を更新し、これらのコンテナ化されたサービスを使用するように構成します。
- テスト実行中:
- テストはこれらのコンテナ化されたサービスを利用して実行されます。
- テスト実行後:
- テストの成否にかかわらず、Testcontainers がこれらのコンテナを破棄する処理を自動で行います。
Testcontainers を使用したテストを実行するための唯一の要件は、Docker API 互換のコンテナランタイムが必要なことです。Docker Desktop がインストールされていて稼働している場合、それで準備は整っています。
Testcontainers がサポートする Docker 環境の詳細については、以下を参照してください: https://www.testcontainers.org/supported_docker_environment/
Testcontainers が解決する問題とは?
Testcontainers は、前述の統合テストに関する課題を解決します。実際のサービスを使用してアプリケーションをテストできるようにすることで、コード変更に対する信頼性を向上させます。
Testcontainers を使用することで得られるメリット:
- 事前に統合テスト用のインフラを用意する必要がありません。Testcontainers API を使用すると、テスト実行前に必要なサービスを提供します。インフラの定義コードは、実際のテストコードのすぐ隣に記述されるため、管理が簡単です。
- 複数のビルドパイプラインが並行して実行される場合でも、各パイプラインは隔離されたサービスセットで実行されるため、データ競合やテストの干渉の問題がありません。
- ユニットテストと同じように、統合テストをIDEから直接実行できます。変更をプッシュしてCIが統合テストを実行するのを待つ必要がありません。
- テストの実行後、Testcontainers がコンテナのクリーンアップを自動的に処理します。
Testcontainers は、Java、.NET、Go、Node.js、Rust、Python など、多くの人気のあるプログラミング言語で使用できます。また、さらなる言語サポートが進行中です。
まとめ
統合テストにおける課題を確認し、モックやインメモリサービスを使ったテストが必ずしも最適ではない理由を理解しました。その後、Testcontainers が統合テストの課題をどのように解決し、実際のサービスを使用したテストを可能にするかについて説明しました。
Testcontainersについてさらに詳しく知りたい方は、公式ウェブサイトをご覧ください: https://www.testcontainers.com/