Java
test
microservices

「Testing Java Microservices」の雑多な感想とConsumer Driven Contractについて

More than 1 year has passed since last update.

はじめに

Microservices固有のテストについて、ManningのTesting Java Microservicesを読んでそのまとめとその他雑多な感想、参考としたツール・発表などを書き連ねてみました。この書籍はMEPA(執筆中の書籍)でV13によります。現時点ではすべての章が執筆しているようでまた各部修正も入っている可能性があるのでご注意ください。

また、元々社内で書いたものをベースにしているのでかなり雑なのはあらかじめご承知ください。

Testing Java Microservices

Chapter1

概要説明だけなので飛ばす

Chapter2

Microservice固有の話はない。
Test Double, Mock, Stab の説明など。

Chapter 3

Unit Test についてなので省略。
ツールも一般的なUnit Testの話だ。

Chapter 4

Component Test の章。
Arquillian を利用しているので、実際にアプリケーションを動かした状態でのテスト実行になる。

Component tests should be designed to verify the functionality of, and between, the internal modules of a microservice, with one exception. The external public facing resource component.

だそうで。

依存もDBなどを除きアプリケーション部分は実際の物を使う。
(DBはテスト用DBに振り向ける)

その後、Arquillian が用意しているRESTのテストフレームワークでRequest/Responseを取得できるのでRequest/Responseが意図通りかのテストを記述する。(p90)
章の見出しではテスト対象は以下に分割される。

  • Remote component
  • Domain component
  • Resource component

それぞれの説明

  • Remote component は外部APIへの依存部分の模様。外部APIはライブラリ(WireMockかな?)でStubとして実行する。
  • Resource component はAPI Endpoint。
  • Domain component は普通にテストする。注意点は以下だそうです。
    • Use a Solitary unit test
    • Never cross boundaries
    • The class under test should be the only concrete class in the test if possible.
  • Persistence component はそのまま永続化部分。
    • これってRepositoryのUnitTestじゃない?

Summary

  • Use the Arquillian workflow to automate as much of the test as possible.
  • Annotate a standard test class with the Arquillian specific annotations to remove boilerplate code.
  • Define repeated elements of deployment archives in base classes or utility classes - Write once and reuse.
  • Inject testable elements into a test class to test against.
  • Create flexible test environments against multiple containers using Maven profiles for plugins.
  • Override default container options in the arquillian.xml file.
  • Use Arquillian extensions to provide a feature rich testing environment.

Integration Test

Integration tests check the interactions between different modules (or classes), usually belonging to the same subsystem, in order to verify that they collaborate as expected when providing a high level feature.

サブシステム(多分単一REST API実装)内で、実際に依存するクラス通しを結合してのテストということらしい。
外部依存している部分はテスト対象外だが、コミュニケーションできることは確認する。
DBを使ったテストもここの章がメイン。Chap4でのPersistence component のところを読み返すと、詳細はChap5参照となっている。

インテグレーションテストで重要なこと(p109)

  • Integration tests verify the connection between internal modules and external components such as a database or other (micro) services, not internal modules.
  • Integration tests use the real external components to validate that communication with real ones is possible.
  • Preparation of the environment for executing tests might be hard or tedious.

Persistence テストはArquillian Persistenceがあるらしい。
https://docs.jboss.org/author/display/ARQ/Persistence?_sscc=t
http://kikutaro777.hatenablog.com/entry/2013/01/06/233526
DbUnitよりも抽象度が高く便利そう。ArquillianがJavaEE向けなので、JavaEE以外の永続化基盤でも利用可能かは調べたほうがよさそう。

Contract Test

契約確認。APIの契約がまもられているかの確認。
API互換性検証。

Inegration Testでもできるが問題がある。

The first one is that the consumer must know how to boot up the provider.

コンシューマがプロバイダの起動方法を知らなければら無い。

The second one is that consumer might depend on several providers. Each provider might have different requirements, for example a database or other services. So starting a provider can imply to start several services and without noticing it converting the integration tests into end-to.end tests.

2点目は、コンシューマがいくつかのプロバイダに依存するかもしれないということ。どちらのプロバイダも違う必須要件がある。例えばデータベースだったり他のサービスだったり。そのため、プロバイダを開始することは、いくつかのサービスを開始することを意味し、統合テストをend-to-endテストに変換することに気付くことなく意味することができます(若干機械翻訳利用)

The third problem and most important one is that you need to create a direct relationship between producer and all its consumers.

感想その他雑感

今の動き(注:当時の所属会社)だとServiceの粒度が大きく複雑に依存することはなさそうな気がする(特定のものに紐付いたサービス)ので、Contract Testはさほぼ重要では無いかも。契約がまもられていることの確認は必要だが、依存関係のメッシュが複雑化しそうにないので普通にテスト書いてもコストはあがらないかも。

契約主体がどこかという観点で以下の分類が出来る。

  • Provider Contract
    • Providerが契約を定義し、Consumerがそれに従う。
  • Consumer Contract
    • Providerが全ての契約をまとめる問題がある。肥大化。
    • 対策としてConsumerが個々に契約を作成する。
    • Consumerは自分が必要な内容だけを契約とする。
  • Consumer Driven Contract
    • あるProviderとのConsumer Contract を集約したものをConsumer Driven Contract とする。
    • Consumer-driven contracts establishes that a service provider is developed from its consumers perspective.
    • コンシューマ主導の契約は、サービスプロバイダが消費者の観点から開発されることを確立します。

Contract Testの一般的にConsumerからみたProducerはMockにしちゃうみたい。
http://techlife.cookpad.com/entry/2016/06/28/164247
本当にリクエスト・レスポンスの形式的なチェックなのかな。
こういうRequestでこういうResponseが返ってくる。Consumer側としてはRequestの細かい内容は別にしてEndpoint毎にResponseの種類がことなり意図したものがどうかを検証する。

Functionalではないので確かによさそうだ。

その他のトピック

Pactについて

https://github.com/realestate-com-au/pact というConsumer Driven Contractを実現するためのツールの説明を読む。

What is it good for?

Pact is most valuable for designing and testing integrations where you (or your team/organisation/partner organisation) control the development of both the consumer and the provider, and the requirements of the consumer are going to be used to drive the features of the provider. It is fantastic tool for developing and testing intra-organisation microservices.

自分たちの組織でコントロール可能なAPIに対してのみ有効らしい。

What is it not good for?

    * Testing new or existing providers where the functionality is not being driven by the needs of the consumer (eg. public APIs)
    * Testing providers where the consumer and provider teams do not have good communication channels.
    * Performance and load testing.
    * Functional testing of the provider - that is what the provider's own tests should do. Pact is about checking the contents and format of requests and responses.
    * Situations where you cannot load data into the provider without using the API that you're actually testing (eg. public APIs). Why?
    * Testing "pass through" APIs, where the provider merely passes on the request contents to a downstream service without validating them. Why?
  • Public API依存のものは効果的では無い。
  • Consumer と Provider チームで仲が悪いと効果的では無い。
  • パフォーマンス・負荷テスト
  • ProviderのFunctional Test。Pact is about checking the contents and format of requests and responses. PactはRequest/Responseのコンテンツとフォーマットしかチェックしない。
  • APIを使わずに内部の状態を取得できない。(Public APIなど)
  • (よくわからない)

Scala版Pactは以下の二つ。

https://github.com/ITV/scala-pact
https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-consumer-specs2

Consumer実装にあわせて言語を選べばよいみたい。

  1. Consumer のテストでPactのMockサーバに対してリクエストを送り、Mockからのレスポンスも含めて記述する。
  2. 実行するとMockがpact fileという、Consumer側のContractが作成される。ここにはConsumerが期待する内容が記述される。
  3. 次にProviderで、前述のpact file 通りに振舞うかがテストされる。

Consumerの実装として、ProviderのAPI戻り値があるから、という理由で受け取るのはやめた方が良さそう。必要なもののに絞り込む。
そうでないとPorviderが肥大化する。

DeNA沖田さんの発表

DeNA 沖田さんの資料。
自分もこれを聞いていたのにわすれていた。

https://speakerdeck.com/okitan/microservicesniokeruapizi-dong-tesutonimatuwaruetosetora

REST APIに対するSpec検査に特化して記述されている。
SwaggerではなくJSON Schemaがメイン。
レスポンスの型という意味では確かにJSON Schema が良きがするが、元同僚の意見としてはコストが高いということ。まあPoCなので。
実際の開発時にJSON Schemaを使うかどうかも検討課題かも。

元同僚の話

元同僚からの話で、UIを介さずにREST APIでシナリオを組んで流すテストもありなのではと。
UIによる動的変化部分をテスト対象外として、あくまでAPIによるデータフローの確認という意味では確かにありかもしれない。実施や作成がUI経由よりも圧倒的にすくなるだろうし。
この方法が有効な部分の検討は必要だが。

そういえば自分も昔JMeterでAPIレベルでの妥当性検証をするためのシナリオテスト組んでいたことを思い出した。

packt-jvm

https://www.slideshare.net/setoazusa/pact-for-jvm
Pact-jvmについてのスライド。

pactで出力されるContract Fileはデフォルトで全てのフィールドがでるようだ。
それはそうだ。Responseのどれが実際に必要かはConsumerの実装みないとわからないから。

pactで出力されたContract Fileを手動で刈り込むことが必要そう。