コントラクトテストが検証すること
両者が想定している構造が一致していることです。フィールド名、型、必須か任意か、ステータスコード。コンシューマーが依存しているフィールドをプロデューサーが削除すれば、コントラクトテストは午前3時ではなくビルド時に失敗します。
サービスが多数あり、それぞれが独立してデプロイされるシステムでは、これは実際に価値があり、エンドツーエンドの統合環境を用意するよりもはるかに低コストです。
あえて検証しないこと
値が正しいかどうか。 構造は正しく、合計値だけが誤っているレスポンスは、すべてのコントラクトを満たします。
フローが機能するかどうか。 個々の呼び出しがそれぞれのコントラクトを満たしていても、その一連の流れが意味をなさない結果を生むことはあります。
ユーザーに何が見えているか。 コントラクトが扱うのは、サービス同士のやり取りです。誰もプロダクトを開いてはいません。
これは批判ではありません。値まで検証するコントラクトテストはデータと密結合になり、絶えず壊れることになります。それこそ、コントラクトテストが避けようとして設計されたものです。範囲が狭いことは、欠点ではなく長所です。
注意すべき失敗パターン
チームがコントラクトテストを導入すると、統合時の失敗が止まり、自信が高まります。その半年後、すべてのコントラクトテストをパスしていたはずの何かが本番環境で壊れます。構造は正しく、振る舞いが誤っていたからです。このとき多くの人はコントラクトテストが機能しなかったと結論づけますが、それは誤った教訓です。
コントラクトテストは役割を果たしています。問題は、振る舞いを検証するものが何もなかったことであり、コントラクト層がうまく機能していたために、その穴が見えにくくなっていたのです。
併せて必要になるもの
実際に動作しているサービスに対する機能検証です。一連の呼び出しが正しい結果を生むか、エラー経路が期待どおりに振る舞うか、認可が適切に効いているか。これは別のレイヤーであり、コントラクトでは答えられない問いに答えます。
こうした検証では、セッション、呼び出し間で受け渡される値、実行順序、クリーンアップへの対応が必要になります。これらについては APIテストのドキュメントで解説しています。
ターミナル
npm install -g @testsprite/testsprite-cli
testsprite setup
何もインストールしたくない場合は、ダッシュボードでも同じことができます。コマンドラインでできるそのほかの操作は、すべて CLI リポジトリにまとめてあります。
コンシューマー駆動と、それが多くの場合に有利な理由
2つのアプローチの違いは、誰がコントラクトを書くかにあります。そしてその差が表れるのは、すぐにではなく1年後です。
プロデューサー駆動のコントラクトは、サービスが提供しようとしている内容を記述します。これは有用ですが、どうしても大きくなりがちです。コントラクトはレスポンス全体を、誰も利用していないフィールドまで含めて記述します。その結果、使われていないフィールドへの破壊的変更でビルドが失敗し、チームはコントラクトの失敗を無視するようになります。これでは本末転倒です。
コンシューマー駆動のコントラクトは、各コンシューマーが実際に読んでいる内容を記述します。コントラクトは小さくなり、失敗は本物になり、そして本当に役立つ副産物が得られます。どのフィールドが誰にとって重要かを示す正確なマップです。このマップがあるからこそ、プロデューサーは自信を持って何かを非推奨にできます。それこそが、誰もが本当に求めていたものです。
それぞれをどこで実行するか
コントラクトテストはビルド時に、高速に、すべてのコミットで実行します。その価値は、統合の前に構造のずれを捕捉することにあるからです。振る舞いの検証は、デプロイ済みの環境に対して、プルリクエストごとに実行します。
重要なのは仕組みそのものよりも、何をきっかけに実行するかです。デプロイのイベントに紐づけておけば、誰かが判断しなくてもすべての変更が検証されます。 GitHub App はダッシュボードからそれを行い、 GitHub Actions のステップはワークフローの中からそれを行います。
コントラクトでは答えられない問いに TestSprite が答えること
値が正しいかどうか、そして一連の流れが正しい結果を生むかどうかです。実際に動作しているサービスをそのインターフェース経由で動かすため、すべてのコントラクトを満たしていながら合計値が誤っているレスポンスも捕捉できます。
具体的な穴になるのは、複数ステップにまたがるフローです。Integration Tests は、あるレスポンスが次の呼び出しの入力になる形で呼び出しを連鎖させ、Dependency Chains は各ケースが必要とするものと生成するものから安全な順序を導き出します。Auto-Authentication は実行中を通じてセッションを維持し、Dynamic Variables は呼び出し間で値を引き継ぎ、Dependency Chains は各ケースが必要とするものと生成するものから実行順序を導き出し、Auto-Cleanup はその実行で作成されたものだけを削除します。
結果として得られるのは、重なり合わない2つのレイヤーです。ビルド時に、低コストで、すべてのコミットで構造のずれを捕捉するコントラクトと、コントラクトがあえて見ていない正しさの問いを捕捉する振る舞いの検証です。
TestSprite はコントラクトテストを行いますか?
いいえ。実際に動作しているサービスに対して振る舞いを検証します。コントラクトテストは別の道具であり、両者は互いを補完します。
マイクロサービスにはコントラクトテストだけで十分ですか?
サービス間の構造のずれに対しては適切な答えですが、複数サービスにまたがるフローが正しい結果を生むかどうかまでは分かりません。
コンシューマー駆動か、プロデューサー駆動か?
コンシューマー駆動のほうが、実際の不具合をより多く捕捉します。プロデューサーが提供しようとした内容ではなく、コンシューマーが実際に使っている内容を記述するからです。
統合テストは今も必要ですか?
数は減らせます。それがまさに狙いです。複数のサービスにまたがるフローを対象とした小さなセットだけを維持し、構造の問題はコントラクトに任せてください。
両側を自分たちで管理している場合はどうですか?
両方を同時に変更できるため、価値は下がります。最も有用なのは、チームがそれぞれ独立してデプロイしている場合です。