[Serverless] サーバーレスのテストの難しさと、契約ベースのテストについて

先日、サーバーレスに深くコミットする堀家さん、Kimさんのお二人によるPodcast "[Serverless NOW](https://serverless.fm/first-talk/)" のファーストトークが配信されました。

自粛中の飯談義で開幕したこの配信ですが、感想としてはとても面白かったです。まあ、私のこんな記事よりも是非 Podcast を直接聞いて欲しくはあるのですが、終盤で特に興味を惹かれるトークがあったので自分の理解のためにもサマりつつ関連キーワードを調査してみようと思いました。

興味を持ったのは、サーバーレスにまつわるテストの難しさに関する話です。難しさの解消を目指すアプローチとして「コントラクト（契約）ベース」によるテストを検証されている、とのお話しがありました。「PACT」と呼ばれるツールを使っているそうです。

「サーバーレスのテスト」「契約ベースのテスト」これらの話題に興味を惹かれ、調べてみることにしました。PACTの使い方に関してはこの記事で言及してません。長くなりそうなので、続編記事として別途アウトプットしようと思ってます。

※ 私自身はサーバーレスやテストについてさほど多くの知見を持っていないので、おや？と思うような場所があったらコメントで指摘いただけると嬉しいです

# なぜサーバーレスのテストは難しいのか

基本的にサーバーレスは非同期の世界であり、クライアントは自分のリクエストがいつ完了したのかを知ることができません。

これは、例えば SQS/SNS/Kinesis などを経由するフローとか、APIの更新系いおいて DynamoDB Streams から別の処理をトリガーしたりするケースを想像するとわかりやすいのかなと思います。

クライアントからのリクエストをトリガーとして、裏では複数のサービスが動いています。でも、リクエストを投げた側からすれば、確かな情報は _（非同期）リクエストを受け付けた_ ことを示すレスポンス程度しかありません。非同期処理のフローが最終的にいつ終了したのかクライアントは知ることができないため、単純なリクエスト／レスポンスのアサーションは行えないわけです。

リクエストの最終的な結果を検証するアプローチとしてまず考えられるのは、ポーリングベースで "結果" が出るのを待ち受けることです。Circle CI などのCIサービスは基本的に同期的にテストが流れていくことを前提としており、既存のツールの仕組みにも乗っけることができます。

しかし、このアプローチは欠点があります。まず「どの程度まで遅延を許容すればよいのか」が不明確である点。また、遅延という不確定要素が入り込むことでテストの安定性が損なわれる点が挙げられます。この E2E テストが失敗した場合、その原因が処理の失敗によるものなのか、はたまた遅延の影響なのか、判断するのは困難です。

非同期でイベントドリブンなサーバーレスアーキテクチャの採用することで、クラウドの真骨頂とも言える優れたスケーラビリティを得られるわけですが、一方で非同期ゆえの難しさ・課題もあるという話です。

# 契約ベースのテスト

「契約ベース」とはなんなのでしょうか？

考え方自体は真新しいコンセプトではないようです。マイクロサービスの文脈や、テスト駆動で有名な t-wada さんの 2016年のセッションで近い話が言及されています。

参考資料

1. [Consumer-Driven Contracts testingを徹底解説！ | Qiita @AHA_oretama](https://qiita.com/AHA_oretama/items/e2c7db87cc5264c701ae)
2. [PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 / PHP Conference 2016 | @Takuto Wada](https://speakerdeck.com/twada/php-conference-2016?slide=85) p.85より

コンセプトの概要を掴むにはこれらの記事に書かれた「契約」のモチベーションに関する説明が非常に参考になります。ので、ちゃんと知りたい方はこれらを読んでください。以下で私なりの理解を補足します。

既存のテストと異なっているのは _テストケースを記述する手段_ にあります。通常であればテストケースに合わせたインプットと期待するレスポンスの組を用意して、パラメータを境界条件を網羅するように書く様子がイメージされます。契約ベースのアプローチでは、Machine Readable な API 仕様のドキュメントを「契約」に見立て、これをベースにテストを行います。

有名どころでは [Swagger](https://swagger.io/) とその関連ツールセットがイメージに近いかと思います。Swaggger の規格で API 仕様を記述することで（対応するツールを用いて）実際のサービスデプロイができたり、あるいはそのSpecの記述をもとに実際にリクエストを投げてみたり、といったことができるようです。契約ベースのテストでは、後者の機能を利用してテストを行うということのようです。PACT はどうも競合に近い立ち位置のようですね。

※私自身はまだ Swagger に触れたことがありませんが、興味ある技術のひとつではあるので API Gateway のデプロイで試してみたいですね

参考: [Swaggerの概要をまとめてみた。 | Qiita @gcyata](https://qiita.com/gcyata/items/342073fa7607fd4082bd)

# サーバーレスに契約テストアプローチを適用する、ということ

調べてみると、マイクロサービスの世界における契約テストの必要性と背景は共通しているように思いました。

PACTの公式ドキュメントによれば、API サービスが複数のサービスコンポーネントが共存する環境（つまり、マイクロサービスのよくある構成）において契約ベースのテストはより大きな威力を発揮する、とあります。

> Although a single client and a single service is a common use case, contract testing really shines in an environment with many services (as is common for a microservice architecture). 
> 
> Having well-formed contract tests makes it easy for developers to avoid version hell. Contract testing is the killer app for microservice development and deployment.
> 
> [When would I use contract testing? | PACT Introduction](https://docs.pact.io/#when-would-i-use-contract-testing)

マイクロサービスにおいて、各サービスコンポーネントの部品がそれぞれでバージョンアップしていくのはごく日常の出来事だと考えられます。API を構成する部品が頻繁に変更されうる前提に立つと、各部品単体だけでなく、E2E で一貫した整合性を担保することの重要性が極めて高いと言えます。

サーバーレスでも、裏が複数のサービスの組み合わせによって構成されている、という点が共通しています。マイクロサービスが複数のコンテナの連携によってクライアントへサービス供給を行うように、サーバーレスも複数のサービスの組み合わせ・つなぎ込みによってサービスを構成しています。このへんが共通のモチベーション（E2Eの一貫性）に繋がっており、ドキュメントベースで E2E をテストできる契約テストへの期待に繋がるのだろうと思いました。

※サーバーレスの場合は、構成要素として採用するサービス自体をドラスティックに変えてしまうこともありえるかもしれませんね。API Gateway の裏にある Lambda や Step Functions 、あるいはその先で連携する Kinesis stream/ SQS/ SNSなど「つなぎ込み」を担うサービス郡の構成を、アーキテクチャの見直しに伴って組み合わせを弄ることもありえるんじゃないかなーと思ったりもします。

今はPACTがサーバーレスに適用可能かどうかの検証中ということですので、その検証過程などの配信も今後の[Serverless NOW](https://serverless.fm/first-talk/)で配信されるかもしれませんね。第２回も楽しみです。