テスト

契約テストとは?Pactによる消費者駆動契約の実装とCI組み込みを実装者向けに解説

契約テストは、APIを呼ぶ側と提供する側が交わした入出力の取り決めを、それぞれのコードベースで別々に検証する手法です。両方を同時に起動せず不整合を検知できるため、E2Eテストが抱える実行時間と不安定さを回避できます。この記事では、E2Eテストやスキーマ検証との検査範囲の違い、Pactファイルが生成され再生されるまでの流れ、pact-js 17.1.0系の実装とprovider state、Pact Brokerとcan-i-deployによるデプロイ判定、採用条件と見送るべき場面を実装者の目線で整理します。

まとめ:契約テストで削れるのはE2Eの本数ではなく調整の手間

契約テストを「E2Eテストの安い代替品」として導入すると失敗します。両者が検知する不整合の種類が違うからです。契約テストが押さえるのは、フィールドが消えた、型が変わった、応答コードが変わった、という構造の破壊。業務フローが期待どおり流れるかは別の層で見る必要があります。

効くのはチーム間の調整コストが下がる点です。「このフィールドを消してよいか」を各チームへ聞いて回る往復が、機械可読な契約とBroker上の検証記録へ置き換わる。削れるのはテストの本数ではなく、人が同期を取る時間です。

導入の順序も決まっています。ツールを入れる前に、契約を保管する場所と、契約が壊れたときに誰が直すかを決める。Pact Brokerを立てずGitで手渡しする構成は、サービスが3つを超えた時点で破綻します。逆に、単一チームが1つのリポジトリで両側を持っているなら契約テストは過剰です。後半でこの見送り条件を構成の水準で示します。

契約テストの定義とE2Eテスト・スキーマ検証との検査範囲の違い

契約テストが検査するのは「約束が守られているか」であって、「システムが正しく動くか」ではありません。

サービス間の合意をコードで固定して破壊的変更を検知する仕組み

契約とは、リクエストの形(パス、メソッド、ヘッダ、ボディ)と応答の形を機械可読な形式で記述したものを指します。Pactの公式ドキュメントは自身を「HTTPとメッセージの連携を契約テストで検証する、コードファーストのツール」と定義しています。設計書を先に書くのではなく、テストコードの実行結果として契約が生まれる点がコードファーストの意味です。

この形式が効くのは、サービスが分割されている環境に限られます。マイクロサービスとモノリスの分割判断を踏まえ、独立してデプロイされる境界がいくつあるかを数えてください。境界が1つなら契約は不要で、コンパイラと型検査が同じ役割を果たします。

E2Eテストとの違いは全系統を起動せず単体テスト速度で回せる点

E2Eテストは関係するサービスを全て起動して実際に通信させます。検知範囲は広い代わりに1本あたりの実行時間が長く、どこかが不安定なだけで全体が赤くなる。サービスが増えるほど、この不安定さは掛け算で悪化します。

契約テストは相手をモックで置き換え、片側だけを動かします。Consumer側ではProviderの代わりにローカルのモックサーバが立ち、Provider側では記録されたリクエストを再生するだけ。どちらも単体テストと同じ速度域で完了します。E2Eテストのベストプラクティスとツール選定で扱う業務フローの検証は残し、連携部分の不整合検知だけを前倒しする。この分業が落としどころです。

OpenAPIスキーマ検証が担う範囲と契約テストが埋める空白

OpenAPI定義に対する検証も形の一致を見る点では似ています。違うのは誰が基準を決めるかです。スキーマ検証ではProviderが公開した定義が正で、消費者駆動契約ではConsumerが実際に使っているフィールドだけが検証対象になります。

この差は運用で表れます。OpenAPI定義に50個のフィールドが並んでいても、実際に読まれているのが8個なら残り42個の変更は誰も困らない。Pactの公式ドキュメントは、この性質を「Consumerが実際に使っている通信部分だけがテストされる」と説明しています。JestとOpenAPIによるAPIテスト自動化の実装と併用するなら、スキーマ検証はProvider単体の入口検査、契約テストは組み合わせ検査として層を分けてください。

観点 E2Eテスト 契約テスト スキーマ検証
起動範囲 全サービス 片側のみ 単体
検知対象 業務フロー 入出力の破壊 定義との乖離
基準の主導権 なし Consumer側 Provider側
実行時間の目安 分単位 秒単位 秒単位

消費者駆動契約でPactファイルが生成され検証されるまでの流れ

契約がどう作られ、どう検証されるかを順に追います。ファイルの移動経路を理解しないまま実装すると、CIの構成でつまずきます。

Consumer側テストの実行でPactファイルが生成される順序

起点はConsumer側です。テストコードで「このリクエストを送ったら、この応答が返るはずだ」という期待を宣言すると、ローカルにモックサーバが立ち上がり、実際のクライアントコードがそこへリクエストを送る。期待どおりの通信が成立したときだけ、内容がJSON形式のPactファイルとして書き出されます。

公式ドキュメントは「契約はConsumerの自動テストが実行される過程で生成される」と明記しています。Pactファイルは手書きの成果物ではなくテストの副産物なので、人手で編集しても次回の実行で上書きされます。

Provider側で契約を再生し実装との不一致を検出する検証工程

生成されたPactファイルは、Provider側のリポジトリで再生されます。検証は、記録されたリクエストを起動中のProviderへ送り、応答が記録内容と一致するかを判定するだけの処理です。一致判定の粒度はConsumer側がマッチャで指定した水準に従うため、粒度設計を誤ると無関係な値の変動で検証が落ち続けます。

Pact仕様v2からv4までの版差とメッセージ連携への対応範囲

Pactには言語実装から独立した仕様があり、公式リポジトリで版ごとの差が定義されています。V2で言語非依存の正規表現マッチと型マッチが導入され、V3でメッセージキュー向けの形式が追加され、V4でさらに機能が拡張されました。使う言語のライブラリがどの版まで対応しているかは導入前に確認してください。HTTPだけならV2で足りますが、KafkaやSQSを挟んだ非同期連携を契約に含めるならV3以降が前提です。

pact-js 17系によるConsumerテストとProvider検証の実装

日本語の解説記事はv12からv14の頃の書き方で止まっているものが多く、そのまま写しても動きません。

pact-js 17.1.0系で変わったエクスポートと最小構成の書き方

npmレジストリを2026-08-07時点で実測したところ、pact-jsの最新版は17.1.0で、公開日は2026-08-06、メジャー版17.0.0は2026-06-25の公開でした。v14前提の記事が参照するPactV4MatchersV3といったエクスポート名は版によって位置が動いてきたため、導入時はインストール先の型定義を開いて確認してください。

最小構成は3手順です。Consumer名とProvider名を指定してインスタンスを作り、期待する相互作用を宣言し、その中で実際のクライアント関数を呼ぶ。テストが緑になった時点で契約ファイルが出力ディレクトリへ書かれます。

マッチャで値そのものではなく型と形を固定するときの粒度の設計

マッチャの使い分けが、契約テストの寿命を決めます。判断基準は単純で、Consumerのコードがその値に分岐しているかどうかです。

  • IDやタイムスタンプ:型マッチのみ。値を固定すると毎回落ちる
  • ステータス文字列:分岐条件に使われるため値を固定する
  • 配列:最小要素数と要素の型を指定し、件数は固定しない
  • 省略可能なフィールド:使っていないなら契約へ含めない

実務でまず押さえるのは上の2つです。IDの固定と分岐用文字列の緩和は、典型的な失敗として繰り返し現れます。プロパティベーステストにおける性質の書き方と同じ発想で、変わってよいものと変わってはいけないものを分けて宣言してください。

provider stateで検証用データを用意する実装と失敗しやすい点

Provider側の検証には、記録されたリクエストが成立する前提のデータが要ります。「IDが42のユーザーが存在する」といった前提をprovider stateとして宣言し、同名のハンドラを登録してデータを投入する仕組みです。

詰まる原因はほぼ2つです。1つは両側で状態名の文字列が一致していないこと。もう1つは前のテストが投入したデータが残り、後続の検証が偶然通ってしまうこと。状態ハンドラには後始末も書き、実行順に依存しない構成にしてください。

Pact Brokerで契約を共有するときの構成と組織側の前提条件

契約テストが定着するかどうかは、コードよりも共有基盤と体制で決まります。

Pact Brokerの自己ホストとPactFlowを選ぶときの費用と運用差

契約ファイルの共有には専用リポジトリのPact Brokerを使います。公式ドキュメントによれば、AlpineベースでPumaを使うDockerイメージが1種類提供されており、自己ホストはこれとデータベースの組み合わせが基本です。

SaaS版のPactFlowを、公式ドキュメントは「Pactのコアメンテナが作った、OSS版Pact Brokerのフォーク」で、UIの改善・フィールド単位の検証結果表示・ユーザーとチームの管理機能が追加されたものと説明しています。判断基準は運用工数の置き場所です。バックアップと版上げを自チームで持てるならOSS版、人を割けないならSaaS版を選ぶ。

両チームのCIが接続されないと契約テストが機能しない組織要件

成立の条件は、Consumer側とProvider側の双方でCIが動いていることです。片側が手動デプロイのままだと、契約が壊れたことに気づく契機が生まれません。CI/CDのパイプライン設計と導入判断が未整備の組織では、先にそちらへ着手してください。

もう1つが責任分界です。Provider側の変更でConsumerの検証が落ちたとき、直すのはProviderなのかConsumerなのか。決めずに導入すると、赤いままのパイプラインを両チームが放置する状態になります。

契約の破棄と再合意を回すためのバージョニングとタグ運用の型作り

契約は永続的な約束ではなく、更新され続けるものとして扱います。Brokerへ公開するときはアプリケーションのバージョンとブランチを添え、どのバージョン同士が検証済みかを記録に残す。これが次章のデプロイ判定の入力になります。

フィールドを削除する手順も決まっています。まずConsumer側の利用箇所を消して契約から外し、その契約がProviderで検証されたことを確認してから実装を消す。順序を逆にすると本番が壊れます。サーキットブレーカーによる障害連鎖の遮断が実行時の防御なのに対し、契約の削除順序は設計時にしか効かない防御です。

can-i-deployをデプロイ判定へ組み込むCD側の安全弁設計

契約テストの価値が確定するのは、デプロイ直前の判定に接続したときです。ここを飛ばすとテストが増えただけで終わります。

Pact Matrixに記録された検証結果からデプロイ可否を判定する

公式ドキュメントは前提をこう説明します。「本番環境へ新しいバージョンをデプロイする前に、これからデプロイしようとしているバージョンに互換性があるかどうかを知る必要がある」。この判定を担うのがcan-i-deployコマンドです。

判定の材料がPact Matrixで、これは「相互に検証されたConsumerとProviderの全バージョンを表にしたときにできる格子」と定義されています。コマンドには対象アプリケーション名とバージョン、向かう先の環境を渡す。返るのは可否の二値なので、CDパイプラインの条件分岐にそのまま置けます。

record-deploymentで環境の実配置を記録しないと判定が崩れる

見落とされやすいのがデプロイ後の記録です。公式ドキュメントは、デプロイ完了後にpact-broker record-deploymentで配置を記録する運用を示しています。この記録がないと、Brokerは各環境に今どのバージョンが載っているかを知りません。

判断材料を欠いたBrokerは、互換性のない組み合わせへ緑を返すか、常に赤を返して判定が形骸化します。can-i-deployだけを入れてrecord-deploymentを省く構成が、判定を無意味にする典型例です。

CIのどの段階に契約検証を置くかで待ち時間と検知漏れが変わる

Consumer側のテストは単体テストと同じ段階で回して構いません。設計が要るのはProvider側の検証をいつ走らせるかです。

  1. 契約の公開を契機にProvider側のCIを起動する。検知は最速だが、他チームの都合でパイプラインが頻繁に走る
  2. Provider側の通常ビルドで最新の契約をまとめて検証する。簡単だが発見が次回コミットまで遅れる
  3. 両方を併用し、公開時の起動は主要Consumerのみに限る

サービスが5つ程度までなら1で始めて構いません。超えたら3へ移行しないとProvider側のCI待ち行列が詰まります。スモークテストのCI組み込みと対象選定と同じく、実行頻度と対象範囲を切り分ける発想が要ります。

Spring Cloud ContractとOpenAPI検証との使い分け基準

契約を誰が書くかという軸で、Pact以外のツールが向く場面もあります。

Spring Cloud Contract 4.3系がJVM側で持つ生成方式の違い

JVM中心の構成ではSpring Cloud Contractが候補です。Maven Centralを2026-08-07時点で確認したところ、spring-cloud-contract-verifierの最新版は4.3.0でした。Pactとの構造的な違いは、契約をテスト実行結果として得るのではなく、Groovy DSLやYAMLで独立した定義として先に書く点にあります。

この方式はProvider側がテストコードとスタブを自動生成できる半面、契約を書く工程が別に増える。Consumerが社内の少数チームに限られ、全てJVMで揃っているならこちらが軽い。言語が混在するならPactです。

比較軸 Pact Spring Cloud Contract OpenAPI検証
契約の作られ方 テストの副産物 DSLで先に記述 定義書が先
主導権 Consumer Provider寄り Provider
言語混在 得意 JVM中心 影響なし
共有基盤 Broker必須 スタブ配布 不要

Consumerが不特定多数の公開APIでスキーマ検証を選ぶ理由

消費者駆動契約は、Consumerの一覧を把握できることが前提です。誰が使っているか分からない公開APIでは、契約を集める作業そのものが成立しません。

この場合はProvider主導へ切り替えます。OpenAPI定義を正として破壊的差分を検査し、廃止予定フィールドへの実アクセスをログで観測して利用がゼロになってから消す。合意の代わりに観測データで安全を担保する形です。

gRPCやGraphQLなどHTTP以外の連携で使えるかの現状整理

Pact仕様V3以降はメッセージ形式に対応しているため、KafkaやSQSを介した非同期連携は契約として記述できます。判定の対象はメッセージのペイロードで、トランスポート層そのものではありません。

gRPCはプラグイン方式での対応が進んでおり、コア実装だけでは完結しない。GraphQLは単一エンドポイントに対してクエリが可変という性質上、消費者駆動契約と相性がよくありません。永続化クエリの一覧を管理対象とし、スキーマの破壊的変更検査で代替するほうが素直です。

契約テストを採用する条件と見送るべき場面の具体的な切り分け方

契約テストは有用な手法ですが、適用範囲は思われているより狭い。ここで判断を言い切ります。

契約テストを採用してよい4条件と最初に着手すべき境界の選び方

次の4つが全て揃ったときに採用してください。1つでも欠けるなら、先にそちらを埋めるほうが投資対効果が高い。

  • 独立してデプロイされるサービスが3つ以上あり、担当チームが分かれている
  • ConsumerとProviderの双方でCIが自動実行されている
  • 連携起因の障害またはリリース遅延が、直近半年で複数回発生している
  • Brokerを運用する担当が決まっている

着手する境界は全部ではなく1組から選びます。基準は障害の発生実績です。不整合で本番が止まった経路、あるいはリリース前の調整に最も時間を使っている経路を1つ選び、そこだけで一巡させる。連携部分の設計や既存APIの棚卸しから相談したい場合は、API開発・システム連携の支援で現状の構成を踏まえた設計から対応しています。

単一チームのモノリスや外部SaaS連携で見送るべき具体的な理由

見送る場面は3つです。まず、1チームが1リポジトリで両側を管理している構成。破壊的変更はコンパイルエラーか既存の単体テストで捕まるため、契約という中間生成物を挟む意味がありません。

次に外部SaaSのAPIを呼ぶ場合。相手のCIで検証を走らせられない以上、消費者駆動契約は成立しません。作れるのはモックの整合性チェックだけなので、バージョン固定と変更通知の購読で備えてください。

3つ目がサービス境界の動いている段階。分割の線を引き直している最中に契約を固めると、書き直しが開発速度を落とす。境界が半年ほど安定してから入れるほうが早い。

契約テストが検知しない振る舞いと性能の欠陥を補うテストの配置

契約テストが緑でも本番は壊れます。契約が保証するのは形の一致だけで、応答内容の妥当性、処理の順序、応答時間、データ整合性は対象外だからです。

補い方は層で分けます。ロジックの正しさは単体テストとミューテーションテストによる網羅性の計測で担保し、業務フローは少数のE2Eテストに残す。性能は負荷試験へ、データ整合性は監視と突合バッチへ振り分ける。この配置を描かず契約テストだけを厚くする構成が、「テストは全部通っているのに障害が出る」状態を生みます。

よくある質問

導入検討でよく挙がる質問を、判断に必要な粒度で整理しました。

契約テストとE2Eテストはどちらを先に導入すべきですか?

E2Eテストが先です。契約テストは連携の形だけを見るため、単独では業務が通ることを保証できません。ただしE2Eは主要な業務フロー数本にとどめ、その状態で連携起因の障害が繰り返し出ているなら契約テストを足します。すでにE2Eが数百本あって不安定なら、契約テストを追加したうえで形の検証に相当するE2Eから減らす進め方が現実的です。

Pactの導入にPact Brokerは必須ですか?

検証の一巡だけならファイルの受け渡しでも動きますが、実運用ではBrokerが事実上の必須要件です。どのバージョン同士が検証済みかの記録、can-i-deployによる判定、契約公開を契機としたProvider側CIの起動が、いずれもBrokerを前提とするためです。サービスが2つの検証段階ならGitでの受け渡しでも回ります。

契約テストはフロントエンドとバックエンドの間でも使えますか?

使えます。むしろ導入しやすい構成です。ブラウザ側のアプリケーションがConsumer、APIサーバがProviderという関係が明確で、チームも分かれていることが多いためです。実装はAPIクライアントの単体テストへPactのモックサーバを組み込む形になります。画面の見た目や表示ロジックは契約の対象外なので、そちらはスナップショットテストの更新運用など別の手段で押さえてください。

契約テストの契約は誰が管理し、変更はどう合意しますか?

契約ファイル自体はConsumer側のテストが生成するため、実質的な管理者はConsumerチームです。ただし合意のプロセスは別に要ります。Consumerが新しいフィールドを契約へ追加すると、Provider側の検証がまず落ちる。この失敗を「実装依頼」と扱うのか「先走り」と扱うのかを導入時に取り決めてください。多くの現場では、事前の合意を経てからConsumer側で書く運用に落ち着きます。

既存のOpenAPI定義がある場合、契約テストは不要ですか?

役割が違うため両立します。OpenAPI定義はProviderが提供する仕様の宣言で、契約テストはConsumerが実際に依存している範囲の記録です。定義に書かれていても誰も使っていないフィールドと、全Consumerが依存しているフィールドを、定義だけでは区別できません。区別できないと破壊的変更の影響範囲を見積もれない。OpenAPI定義は入口の形式検査、契約は変更可否の判断材料として使い分けてください。

関連記事

資料請求

RELATED POSTS 関連記事