ひとことで言うと#
APIの呼び出し側(Consumer)と提供側(Provider)の間で**「このリクエストにはこのレスポンスを返す」という契約(Contract)を定義し、自動テストで検証**する手法。サービスを単独でテストでき、結合テストの遅さと不安定さを解消する。
押さえておきたい用語#
- Consumer(コンシューマー)
- APIを呼び出す側のサービスのこと。「こういうリクエストを送り、こういうレスポンスが欲しい」という期待を契約として定義する主体。
- Provider(プロバイダー)
- APIを提供する側のサービスのこと。Consumerが定義した契約を満たしているかを検証される対象。
- Pact Broker
- 契約ファイルを一元管理するサーバーのこと。Consumer・Provider双方のバージョン管理と互換性チェックを仲介し、
can-i-deployコマンドでデプロイ可否を自動判定する。 - CDC(Consumer-Driven Contract)
- Consumerが主導して契約を定義するアプローチのこと。Providerは「誰が何を必要としているか」を正確に把握でき、不要な破壊的変更を防げる。
コントラクトテストの全体像#
こんな悩みに効く#
- バックエンドAPIの変更が、フロントエンドやモバイルアプリを壊してしまう
- 結合テストが遅くて不安定で、CIパイプラインのボトルネックになっている
- サービス間のAPI仕様がドキュメントと実装で乖離している
基本の使い方#
コントラクトテストの主流はConsumer-Driven Contract Testing(CDC)。
- **Consumer(呼び出し側)**が「こういうリクエストを送って、こういうレスポンスが欲しい」を定義する
- **Provider(提供側)**がその契約を満たしているかを検証する
- 契約はJSON形式のファイル(Pact file)としてバージョン管理される
ポイント: Consumerが主導するのがポイント。Providerは「誰が何を必要としているか」を正確に把握できる。
Consumer側でMockサーバーに対してテストを実行し、契約ファイルを生成する。
- テストフレームワーク(Pact、Spring Cloud Contract等)を導入
- 「このエンドポイントにGETを送ると、こういうJSONが返る」をテストとして記述
- テスト実行時にMockサーバーが契約通りのレスポンスを返し、Consumer側のコードが正しく動くことを検証
- テスト成功後、契約ファイルが自動生成される
ポイント: Consumer側のテストはProviderを起動せずに実行可能。高速で安定。
生成された契約ファイルを使って、Providerが契約を満たしているか検証する。
- 契約ファイルをPact BrokerまたはCI経由でProvider側に共有
- Providerの実際のAPIに対して、契約で定義されたリクエストを送信
- レスポンスが契約通りか(ステータスコード、ボディの構造、型)を自動検証
ポイント: Provider側のテストで契約違反が検出されたら、デプロイをブロックする。
コントラクトテストをCI/CDに組み込み、自動化する。
- Pact Brokerで契約ファイルを一元管理
- Consumer変更時: 新しい契約がProviderに破壊的影響を与えないかチェック
- Provider変更時: 既存の全契約を満たしているかチェック
can-i-deployコマンドで、デプロイの安全性を自動判定
ポイント: **Pact Brokerのcan-i-deploy**が「このバージョンをデプロイしても大丈夫か?」に自動で答えてくれる。
具体例#
状況: 注文サービス(Consumer)が在庫サービス(Provider)のAPIを呼び出している。在庫サービスがレスポンスのフィールド名を変更したことに気づかず、本番で注文処理がエラーになった。
Consumer側(注文サービス)のテスト:
リクエスト GET /api/stock/SKU-001 に対して、レスポンスに sku(文字列型)、quantity(整数型)、available(真偽値型)が含まれることを定義。
Provider側(在庫サービス)の検証: 在庫サービスがquantityをstock_countにリネームしようとした時点でテストが失敗しデプロイがブロックされた。
結果: API互換性の破壊がデプロイ前に100%検出されるようになった。結合テスト環境のメンテナンスコストが80%削減。
状況: Web・iOS・Androidの3チームが同一のバックエンドAPIを利用。バックエンドチームがレスポンスを変更するたびに、3チーム中少なくとも1つで障害が発生していた(月平均4件)。
導入: 3チームがそれぞれConsumerとして契約を定義。Pact Brokerで一元管理。バックエンドのCIに3つの契約検証を組み込み。
効果: バックエンドチームはPRの時点で「この変更がWebに影響するがiOSには影響しない」と即座に把握可能に。API変更起因の障害が月4件→0件に。バックエンドチームの「誰が何を使っているか分からない」問題が完全に解消。
状況: マイクロサービス8つの結合テスト環境を維持。全サービスを起動してのE2Eテストに45分かかり、環境の不安定さでCIの成功率は65%。月のインフラコストは**$2,000**。
コントラクトテスト導入後:
- 各サービスペア間にPactテストを導入(合計12件の契約)
- 各サービスのCIで個別にコントラクトテストを実行(平均2分)
- E2Eテストは主要な3フローに絞り、週1回のみ実行
結果: CI時間が45分→13分(70%短縮)。成功率が65%→96%。結合テスト環境のインフラコストを月$2,000→$300に削減。
やりがちな失敗パターン#
- 契約を細かく定義しすぎる — レスポンスの全フィールドの値を固定で検証すると、Providerの些細な変更でテストが壊れる。型とフィールドの存在だけを検証し、値は柔軟に
- Pact Brokerを使わない — 契約ファイルをGitで直接共有すると、バージョン管理が煩雑になる。Pact Brokerで一元管理する
- 結合テストの完全な代替と考える — コントラクトテストは「インターフェースの互換性」を保証するが、エンドツーエンドの業務フローは検証しない。少数の結合テスト + 多数のコントラクトテストの組み合わせが最適
- Provider側の検証をCI必須にしない — Consumer側だけテストしてProvider側を任意にすると、契約違反が本番に到達する。Provider側のCIにも必ず組み込む
まとめ#
コントラクトテストは、マイクロサービス間のAPI互換性を 「テストで保証する」 仕組み。Consumerが契約を定義し、Providerがそれを検証するCDCアプローチで、デプロイ前に破壊的変更を確実に検出できる。重い結合テストの代わりに軽量なコントラクトテストを活用し、安全かつ高速なデプロイサイクルを実現しよう。