APIはPHPコントローラや公開済み仕様だけで定義されるものではありません。他システムがすでに組み込んだ期待、すなわちルート、フィールド名、HTTPコード、エラー形式、ページをたどるために必要な順序によっても定義されます。PHP APIのコントラクトテストは、コードをマージまたはデプロイする前に、こうした期待を自動検証へ変換します。
目的は、あらゆる進化を阻止することではありません。変更がコンシューマーから観測可能な合意を変えるかを検出し、互換性を維持する、移行を導入する、またはインターフェースをバージョニングする、と意識して判断することです。これは、複数チームが関わる内部API、B2B統合、公開から数時間後に障害が現れる可能性のある非同期フローで特に重要です。
コントラクトテストで解決できることと、置き換えられないもの

コントラクトテストは、プロバイダーとコンシューマーが一つのインタラクションで一致していることを確認します。有効なリクエストが与えられたとき、プロバイダーが合意済みの構造、型、ルールを持つレスポンスを返すことです。逆方向では、コンシューマーが必要なリクエストを宣言し、プロバイダーがそれらを処理できることを検証できます。
このアプローチは、ユニットテストが見逃しがちな非互換性を検出します。ユニットテストは、シリアライザがcustomer_idを返すことを確認できますが、以前にcustomerIdを期待していたコンシューマーがそのフィールドを理解し続けることまでは証明しません。ローカル統合テストはエンドポイントを対象にできますが、各統合の実際の前提を必ずしも取り込むわけではありません。
これらは他の統制を置き換えません。
- ユニットテスト:ドメインルール、バリデーション、変換のため。
- 統合テスト:データベース、キュー、キャッシュ、認証、接続済みサービスのため。
- エンドツーエンドテスト:管理された環境における完全な重要フローのため。
- セキュリティおよび性能テスト:認可、不正利用、データ露出、レイテンシ、容量のため。
- 本番環境の可観測性:廃止予定の挙動をなお使用しているコンシューマーを検出するため。
また、コントラクトはレスポンスがビジネス上正しいことを保証するものでもありません。宣言された形式とセマンティクスを維持していることを保証します。そのため、空のスキーマだけでなく、関連するルールを表す例を伴わせる必要があります。
APIコントラクトを構成するもの
コントラクトとは、コンシューマーが観測でき、依存し得るあらゆる挙動です。成功レスポンスのJSONだけに限定すると、最も頻繁な破壊を取りこぼします。各操作について、少なくとも次の要素を合意することが望まれます。
- リクエスト:メソッド、ルート、クエリパラメータ、ヘッダー、ボディ、必須フィールド、形式、制限。
- レスポンス:HTTPコード、関連するヘッダー、構造、型、任意フィールド、nullを許容するフィールド、日付・通貨・識別子の形式。
- エラー:ステータスコード、エラーボディ、安定した機能コード、それを発生させる条件。人向けテキストは変更できますが、
validation_failedのようなコードは自動化により適しています。 - ページネーションとフィルタリング:
limit、カーソルまたはページの意味、順序の安定性、次カーソルの表現、空集合の扱い。 - 認証と認可:許可されるメカニズム、必須ヘッダー、スコープまたは権限、無効な認証情報・認証情報の欠如・アクセス拒否の違い。
- イベントとWebhook:イベント名、ペイロードのバージョンまたはスキーマ、署名、再試行、イベント識別子、順序が保証されないこと、冪等性への期待。
プロパティの必須性とnullの許容は、独立したルールです。フィールドは必須でnullを許容でき、任意で出現時には許容できず、または任意で存在する場合に許容できます。同様に、プロパティの省略、nullを持つプロパティ、空文字列を持つプロパティは異なる状態です。コンシューマーがそれらを異なる方法で解釈するなら、コントラクトで表現しテストする必要があります。
コンシューマーを壊し得る小さな変更
変更はプロバイダーから見ると無害に見えても、生成されたクライアント、厳格なバリデータ、またはビジネスロジックとは互換でないことがあります。たとえば整数を42から"42"のような文字列に変えると、比較とスキーマが壊れます。フィールドを任意にしても、それだけではnullを受け入れるかは決まりません。前者のルールはプロパティが存在しなければならないかを定め、後者は含まれる場合に有効な値を定めます。
ほかにも、以前は201だったところで200を返す、空リストをnullに置き換える、小数の精度を変更する、エラーコードを改名する、通知なくページネーション順序を変える、といったリスクのある変更があります。フィールドの追加は寛容なリーダーには通常互換ですが、コンシューマーがクローズドスキーマを検証する場合や、完全なボディに対して署名を計算する場合には互換ではありません。
互換性は単独の規則ではなく、実際の合意に依存します。既知のコンシューマー、宣言された許容性、フローの重要度に従って各変更を分類することが望まれます。その情報が分からない場合は、好都合な仮定ではなくリスクとして扱う必要があります。
仕様、コンシューマーコントラクト、または両方を選ぶ
たとえばOpenAPI記述のようなインターフェース仕様は、ルート、操作、パラメータ、スキーマ、レスポンスの共通ソースとして適切に機能します。パイプラインで検証し、基準バージョンに対する非互換な変更を検出できます。多数のコンシューマーがある場合、または同じ定義からクライアントとドキュメントを生成する場合に有用です。
しかし、スキーマが各コンシューマーにとって重要なことを常に捉えるとは限りません。フィルターの組み合わせ、ビジネス条件に対する特定のエラー、例の値への依存などです。コンシューマー駆動コントラクトは、各コンシューマーが必要とする具体的なインタラクションを宣言します。プロバイダーはそれらを実装に対して検証します。
両方のレベルを使うことは、多くの場合合理的です。仕様が一般的な表面を統治し、コンシューマーコントラクトが高価値フローやスキーマへ還元しにくいセマンティクスをカバーします。各成果物には明確な責任者が必要です。コード変更時に仕様が更新されなければ、信頼できる情報源ではなく架空のドキュメントになります。
代表例と境界ケース
コントラクト例は、本番データではなく、構造が現実的なデータを含める必要があります。注文リソースでは、要素を持つケース、有効であれば空のケース、合意済み形式の識別子、その慣例ならタイムゾーン付きの完全な日付を含めます。認可拒否、バリデーション失敗、存在しないリソース、最後のページネーションのケースも追加します。
ランダム識別子、現在時刻、JSONプロパティの順序など、正当に変わり得る無関係な詳細を固定することは避けてください。安定している部分には正確なアサーションを、可変部分には明示的な許容性を使用します。各例は既知のニーズに答えるべきです。作り物のレスポンスを大量に集めても、信頼性を高めずに保守負担を増やします。
既存PHP APIでの段階的な実装
価値を得る前にAPI全体をモデル化する必要はありません。まず、内部アプリケーション、B2Bクライアント、バッチプロセス、モバイルアプリケーション、自動化、Webhook受信者というコンシューマーの棚卸しから始めます。所有者、連絡チャネル、使用する操作、重要度、更新能力を記録します。
次に、リソースを作成または変更するエンドポイント、ユーザーを認証するエンドポイント、財務プロセスを支えるエンドポイント、自動化を起動するエンドポイントを優先します。レビュー済み仕様と、再現可能なAPIインスタンスに対するテストを通じて、現在の挙動のベースラインを確立します。PHPでは、テストはサービスクラスを直接呼び出すのではなく、アプリケーションの実際のHTTPレイヤーを実行しなければなりません。コントラクトにはルーティング、ミドルウェア、シリアライゼーション、例外処理が含まれます。
POST /api/orders
Authorization: Bearer token
Content-Type: application/json
{"items":[{"sku":"ABC-1","quantity":2}]}
201 Created
{"id":"ord_123","status":"pending","items":[...]}前述の例は、どのフィールドが必須か、idが常に文字列か、無効なSKUでどのエラーを返すか、初期状態が保証されるか、といったルールを伴う場合にのみ有用です。アサーションに変換すべきなのはこれらのルールです。
継続的インテグレーションおよびデプロイ前の検証
承認済みコントラクトに実装が従わない場合、パイプラインはマージ前に失敗しなければなりません。実用的なフローには、ユニットテストの実行、制御された依存関係の起動、テスト設定でのPHP APIの起動、仕様・プロバイダーコントラクト・代表的なコンシューマーコントラクトの検証が含まれます。失敗を再現可能にするため、テストは分離され決定的なデータを使う必要があります。
変更リクエストでは、提案された仕様を公開済みバージョンとも比較し、ルートの削除、存在要件の強化、null許容の変更、型変更、削除されたレスポンスを示します。診断は、操作、インタラクション、違反したルールを示すべきです。単なるスキーマエラーでは調査が多く必要になります。
デプロイ前には、異なるビルドではなく、公開する成果物に対して同じスイートを実行します。デプロイ後は、エラーコード、クライアントから報告されるデシリアライズ失敗、バージョン利用状況、非推奨ルートへのトラフィックを監視します。事前検証はリスクを低減しますが、実トラフィック下での挙動確認を置き換えるものではありません。
互換性、非推奨化、安全な廃止

変更に互換性がない場合は、明示的な移行を優先してください。以前のフィールドを維持しながら新しいフィールドを追加する、新しい操作またはバージョンを導入する、利用シグナルで裏付けられた廃止日を伝えることができます。非推奨化は、所有者、コミュニケーション、測定を伴う運用期間であり、ドキュメント内の注記だけではありません。
保留中のコンシューマーを特定できない場合、またはフローが重要である場合は、期限が過ぎたという理由だけで挙動を廃止してはいけません。可能な場合は、レスポンスを変更せずに旧利用を特定するため、制御された警告とメトリクスを公開します。新バージョンの段階的公開により、変更を広げる前にエラーを観測し、コントラクトを修正できます。
最もよくある誤りは、成功レスポンスだけをテストすること、人向けメッセージをエラーコードの代わりにモデル化すること、すべてのクライアントが新規フィールドを無視すると仮定すること、実際または代表的なコンシューマーを関与させないことです。コントラクトテストは、両者が維持する合意を反映し、通常のデリバリー条件として実行されるときに価値をもたらします。



