Consumer Driven Contract TestingをSpring Boot × pact-jvmで試してみる

Consumer Driven Contract Testing

What is Consumer Driven Contract(CDC)

Consumer Driven Contract Testingとは、Consumer(Clinet)が依存するProvider(API)に対して、期待する挙動をContractとして共有し、Provider側では、Consumer側で定義されたContractに準拠する事を担保するテスト手法になります。

Consumerは、Contractを元にしたモックサーバーを用いてテストを実施し、Provider側ではContractに準拠している事をテストします。

メリット

サービス間連携においての破壊的変更の検知が、その他のテスト内で行うのと比べてやりやすいです。

例えばマイクロサービスなど、特定のアプリケーションや機能を実現するのに、複数のサービスが関わる状況においては、Clentが依存する外部APIが複数存在、もしくはその逆で自チームが管理するAPIに依存するClientが複数存在する状況があると思います。

その中で自分たちが行う変更がClientに影響が出ないか、また依存APIがそのような変更を行っていないか検知する仕組みとして、Consumer Driven Contractは便利です。

他のテストのレイヤーと比べてみると

Unitテストでは、基本的にProviderの最新の変更に追随することは難しいです。
APIをモックしてテストを実行することになりますが、そのモックは完全にConsumer内で完結してしまっています。 例えば、API側の変更に応じて、Client側のUnitテストで利用するスタブデータを更新する事も出来ますがClient側のUnitテストを実行しなければAPIの変更が破壊的変更であることは検知できません。
マイクロサービスとして各サービスが独立してデプロイを行いたい環境で、Providerのリリース時にClient側のテストを実行するのは本末転倒感があります。

e2eテストに関しては、厚めに用意すればほぼ検知できるとは思います。
しかし、e2eテストは、Unitテスト等と比べて実行コストの高いテストです。 一つのマイクロサービスをテストしたいのに、全てのマイクロサービスを用意する必要があり、実際に通信を行うのでUnitテスト等と比べると実行時間もかかります。 結果としてフィードバックを得られるのが遅くなってしまうのがネックです。

各システム単位でのIntegrationテストを実際にステージング環境等に対して繋いで行っても、Unitテストと同じく、Consumer側のテストを実行しないことには気づくことが出来ません。
Client側でどう使われているかを徹底的に再現した上で、Provider側のテストを行なえば、Provider側で破壊的変更を検知できる可能性はありますが、Provider側で複数のClientのユースケースを把握しておくのは非常に辛い気がします。
またProviderの破壊的変更に関してはConsumerの方が関心が高いので、Consumer側からProviderに守ってほしいルールを定められる方がより効果的なはずです。

このように、今回挙げたテストのレイヤーでは破壊的変更を検知するのにはなかなか手間ですが、上記の課題に対して、Consumer Driven Contract Testingは非常に有効な対策になり得ます。

What is Pact

pactはConsumer Driven Contract Testingを実現するためのツールの一つになります。

複数の言語での実装が存在しますが、今回はpact-jvmを用いてSpring Bootで実装しているAPI間でのConsumer Driven Contract Testingをどのように実現するのか試してみます。

Contractを共有する仕組み

Contractを共有する仕組みとして、pact-brokerというOSSを用いることが出来ます。

これを使う事で、Consumer側でContractが生成された際に、pact-brokerに対してファイルをアップロードし、
Providerではpact-broker経由で自身の関連するContractファイルを引っ張ってくる事で、Consumer-Provider間でContractを共有することができます。
docker-imageも提供されています。

参考

https://martinfowler.com/articles/consumerDrivenContracts.html https://techlife.cookpad.com/entry/2016/06/28/164247