マイクロサービスにおけるテスト方法の調査

2020-08-23T22:31:43

マイクロサービスにおけるテスト方法の調査

マイクロサービスを使った結合試験の難しさはそのテストすべき境界の数や、関係コンポーネントの多彩さにあります。

一般に、マイクロサービスの品質担保は下記4つのテストにより担保されています。

  • Unitテスト:クラス単体のテストを指す
  • Integrationテスト:テスト対象と外部依存箇所(スタブを使う)のテスト
  • Componentテスト:マイクロサービス単体の受け入れテスト。テスト対象以外のサービスはスタブを使う
  • End-to-Endテスト:システム全体の受け入れテスト。本番同様の環境となる

とはいえ、自分はIntegrationテストとComponentテストの差がはっきりわかりませんでした。そのため、IntegrationテストとComponentテストの差や、それぞれの具体的な方法に関して調べ、まとめてみることにしました。

さて、導入が長引きましたが本記事で取り上げたいのは「Componentテスト、「E2Eテスト」です。

参考にした情報について

先に、この記事を書くにあたって参照した情報を載せておきます

  1. https://kencharos.hatenablog.com/entry/2019/02/26/013545 (2019/02/26)
    • Chris Richardson 氏の Microservices Patternsの読んだまとめを書かれています。IntegrationではCDCを、Componentではそのテスト構成とテストツール( Cucumber/Gherkin)について触れています。
  2. https://martinfowler.com/articles/microservice-testing/ (2014/11/18)
    • マイクロサービスにおけるテスト戦略について書かれた資料です。時期からして黎明期のはずですが、2019年前後の資料と比べても見劣りはありませんでした。
    • それぞれのテストフェーズについて、テストで注目すべきモジュールに関してのピックアップがあり、わかりやすくまとまっています。
  3. https://www.infoq.com/articles/twelve-testing-techniques-microservices-intro/ (2019/08/15)
    • パート3まであります。テストの方法、そのトレードオフ、具体事例で構成されます。
  4. https://www.simform.com/microservice-testing-strategies/
    • マイクロサービスのテスト戦略についてまとめてくれています。謎のプロフェッショナル集団の記事です。最近の記事のようですが、資料2を参照しています。
  5. https://www.continuousdeliveryconsulting.com/blog/end-to-end-testing-considered-harmful/ (2015/12/12)
    • 参考資料3のパート3の記事からリンクされます。有害なE2Eテストに関する記事であり、E2Eテストのデメリットに関してフォーカスしています

Componentテストとはなにか

多くの参考記事において、IntegrationテストとComponentテストは区別されますが、その具体的な方法に関しては似通っている事が多くありました。
実際、最初のうちは全く区別がつかなかったのですが、違いはどうやらテストスコープにありそうです。

Integrationテストとの差について

Integrationテストについて、参考資料2では下記のように説明しています。

An integration test verifies the communication paths and interactions between components to detect interface defects.

https://martinfowler.com/articles/microservice-testing/#testing-integration-introduction

すなわち、
「Integrationテストでは、通信経路やコンポーネント間の相互作用を検証し、インターフェースの欠陥を検出する」、とあります。

実際、Integrationテストで挙げられるテスト観点は下記の画像で説明されています。(詳細は引用元で確認ください)

この画像において、Integration Test Boundary(結合テストの境界)とされているのは、外部サービスと外部データソースです。例として下記のような話題が上がっています。

  • HTTPヘッダの欠落
  • リクエスト、レスポンスボディの不一致
  • 特殊ケースのエラー、タイムアウト例外
  • コードのスキーマと、データストアで利用するスキーマの一致
  • ORMマッピング
  • ネットワーク異常時の際のデータストア例外

より具体的には

参考資料4では、参考資料2を参照しつつも、最近の事例に関して触れています。曰く、

One of the most important aspect of service to service testing is- tracing. What exactly happens in the Integration test is that each request would touch multiple services before it circles back to the user with a response. Therefore it becomes imperative to have observability and monitoring of request across service. Tools like Jaegar can help you with tracing.

https://www.simform.com/microservice-testing-strategies/#integration

「サービスからサービスへのテストの最も重要な側面の一つは、トレースです。統合テストで何が起こるかというと、各リクエストはレスポンスを持ってユーザーに戻る前に複数のサービスに触れることになります。したがって、サービス間でのリクエストの観測性と監視が不可欠になります。Jaegarのようなツールは、トレースであなたを助けることができます」

とあります。(分散トレースはたしかに重要な課題です)

ただ、同時に複雑な外部依存関係を持つプロダクトに対し、IntegrationTestの費用対効果は薄いという意見に関しても紹介があります。

Componentテストの説明

一方で、Componentテストは下記のように説明されます。

A component test limits the scope of the exercised software to a portion of the system under test, manipulating the system through internal code interfaces and using test doubles to isolate the code under test from other components.

https://martinfowler.com/articles/microservice-testing/#testing-component-introduction

つまり、
「Componentテストでは、テスト対象のソフトウェアの範囲をテスト対象のシステムの一部に限定し、内部コードインターフェースを通じてシステムを操作し、(複数の)テストダブルを使用して、テスト対象のコードを他のコンポーネントから分離する」

とあります。

テスト対象のレイヤは下記のように説明されます。
(インプロセス、アウトプロセスと言う2つの方法が示されており、下記はインプロセスのものです)

ComponentTest

例としてあげられているのは、

  • マイクロサービスを外部から隔離するために、クライアントの代わりにテストダブルを利用する(特定の要求が来たら、事前に用意した応答を返す)
  • 外部データストアはインメモリなものを利用する

事が書かれています。

一点、この資料で現在の資料と乖離が見られるのはCDCをComponentテストなどと同レイヤにおいて述べている点です。
現在のテストフレームワークでは、CDCをサポートしているフレームワークもあり、それらはWiremockなどと組み合わせることでIntegration TestやComponent Testのレイヤでも利用が可能です。

より具体的には

参考資料4ではComponentテストの実装に関する問題として、「異なるマイクロサービス間のIFをテストするとして、それは常にテスト環境と本番環境で一致しているのか」、「本番環境とテストダブルの動作を保証し続けられない」というものを挙げています。

前者の答えとして、Generation Gapパターンに似た方法を挙げているようです。すなわち、外部公開したIFを実装すること前提にしているようです。例えば、Swaggerで自動生成したインタフェースの実装を強制することと等価なのかな、とおもいます。

後者の答えで挙げているのは、Consumer Driven Contractのように見えます。

考察

IntegrationTestで述べられているテスト範囲は、完全に外部接続点に集約していると思っていいでしょう。たとえば、外部API呼び出しを行う箇所やDBとの接続点です。

SpringBootでは、実装の仕方によりますが、ドメインサービスやリポジトリにおけるテストにあたるでしょう。
今までの自分の認識ではこれも単体テストにすぎず、Integrationテストというのは内部結合試験に当たると思っていました。

しかし、マイクロサービスのテストにおいては、それら(Integration Test Boundary)を外部依存性を持つ箇所として定義し、それのためにテストを行うべき箇所として定義しています。

ここの理解ができれば、ComponentTestというのはある意味自明です。内部結合試験です。
すなわち、何らかの方法でHttpサーバを起動し、Controllerに対して擬似的なリクエストを送りテストをするということです。

思うに、IntegrationにしろComponentにしろConsumer Driven Contractはうまく機能するように思います。
IntegrationTestにおいては、さらにSwaggerを利用したGeneration Gapパターンをくみあわせることで外部境界に関するテストはかなりやりやすくなるのではないでしょうか。

ComponentTestにおいては、参考資料1によるとCucumber/GherkinのようなBDD(behavior driven development)なテストフレームワークを推奨しているようです。
これは、なるべくシナリオベースの試験コードを書くことが重要であるという思想からのようです。

End-to-Endテスト

E2Eテストに関して、参考資料2は下記のように説明しています。

An end-to-end test verifies that a system meets external requirements and achieves its goals, testing the entire system, from end to end.

https://martinfowler.com/articles/microservice-testing/#testing-end-to-end-introduction

「End-to-End Testでは、システムが外部要件を満たし、目標を達成しているかどうかを検証し、システム全体を端から端までテストします」

記事中では、あくまでもシステムをブラックボックスとして扱うこと、ビジネス要件をクリアするために行われるものであることが書かれています。

気づきとしては、E2Eテストは本番相当の環境で行われることが前提である認識でしたが、副作用等でテスト対象に組み込みにくいならばスタブとしてもいいと言う旨が書かれています。(信頼性は落ちるが、テストスイートの安定性はあがる)

実装の難しさについて

E2Eの実装はコストが大きいことを注意点として挙げており、下記の注意事項が記載されています。

  • できる限り少ないテストを書く
    • ひとつあたりのテストのコストが高いから、費用対効果を考えること
  • ペルソナとユーザージャーニーに重点を置く
    • ユーザが最も重視する部分をテストする
  • あなたの目的を賢く選ぶ
    • 不安定な箇所を頑張ってテストするよりスタブで逃げる
  • テストをデータ非依存にする
    • E2Eテストの困難はほとんどデータにある

具体的には

参考資料1ではCucumberが、参考資料2ではSeleniumが、それぞれ挙げられています。

事例としては、参考資料3のパート3をみてみると、E2Eテストの導入は一般的に行われているように見えます。一方で、参考資料5のように、E2Eのリスクに関して触れている事例もありました。

End-To-End Testing is used by many organisations, but relying on extensive end-to-end tests is fundamentally incompatible with Continuous Delivery. Why is End-To-End Testing so commonplace, and yet so ineffective?

https://www.continuousdeliveryconsulting.com/blog/end-to-end-testing-considered-harmful/

引用の引用になります。
「E2Eのテストは多くの組織で使用されていますが、大規模なE2Eのテストに依存することは、継続的なデリバリとは根本的に相容れません。E2Eがこれほどまでに一般的であるにもかかわらず、効果がないのはなぜでしょうか?」

個人的には楽がしたいと思って調べたのでこの一文は痛烈です。
記事中ではE2Eが巷で言われるほど効率の良いものにならない理由と、継続的インテグレーションとの相性の悪さについて語られているようですが、全ては読めていません。

また、End-to-End Testにフォーカスをあてて調べたいと思います。

成果

マイクロサービスにおけるIntegration Test

外部サービス、データストアとの境界、IFに注力してテストすること。
Consumer Driven ContractやGeneration Gapパターンと相性が良さそう。

Springなら、SpringCloudContractと合わせて、Swagger(OpenAPI)のサポートもあるようだ。これを使えば、外部境界をほとんど自動生成のコードを中心として実装、テストすることが可能になってくると思う。

https://github.com/springframeworkguru/spring-cloud-contract-oa3

マイクロサービスにおけるComponent Test

内部結合テスト。マイクロサービス単体に関するテスト。
Consumer Driven Contractと相性が良さそう。

BDD系のテストフレームワークの名前を多く見たが、BDDに詳しくないのでイメージつかず。また調べたい。

マイクロサービスにおけるEnd-to-End Test

CucumberやSeleniumなどの名前が上がったり、採用事例も多く見かける一方で、アンチE2Eな意見も多くある。

実装コストとのバランスを見たら使えるのか、使わないことを推奨なのかはまだ判断つかず。追加調査したい