Spring Cloud Contract の導入
Spring Cloud Contract は、TDD をソフトウェアアーキテクチャのレベルにプルアップます。これにより、コンシューマー主導およびプロデューサー主導の契約テストを実行できます。
ヒストリー
Spring Cloud Contract になる前は、このプロジェクトは最も正確な [GitHub] (英語) と呼ばれていました。( Codearte [GitHub] (英語) ) のマルチングシェジザク (英語) とヤクブクブリンスキー (英語) によって作成されました。
0.1.0
リリースは 2015 年 1 月 26 日に行われ、2016 年 2 月 29 日の 1.0.0
リリースで安定しました。
テストの課題
前のセクションのイメージの左上隅にあるアプリケーションをテストして、他のサービスと通信できるかどうかを確認したい場合は、次の 2 つのいずれかを実行できます。
すべてのマイクロサービスをデプロイし、エンドツーエンドのテストを実行します。
単体テストおよび統合テストで他のマイクロサービスをモックします。
どちらにも利点がありますが、多くの欠点もあります。
すべてのマイクロサービスをデプロイし、エンドツーエンドのテストを実行する
利点:
本番をシミュレートします。
サービス間の実際の通信をテストします。
短所:
1 つのマイクロサービスをテストするには、6 つのマイクロサービス、いくつかのデータベース、その他の項目をデプロイする必要があります。
テストが実行される環境は、単一のテストスイートに対してロックされます (その間、他の誰もテストを実行できなくなります)。
実行には長い時間がかかります。
フィードバックはプロセスの非常に遅い段階で得られます。
これらはデバッグが非常に困難です。
単体テストおよび統合テストで他のマイクロサービスをモックする
利点:
彼らは非常に迅速なフィードバックを提供します。
インフラストラクチャ要件はありません。
短所:
サービスの実装者は、現実とは何の関係もない可能性のあるスタブを作成します。
テストに合格しても本番環境に失敗しても、本番環境に移行できます。
上記の課題を解決するために開発されたのが Spring Cloud Contract です。主なアイデアは、マイクロサービス全体をセットアップする必要なく、非常に迅速なフィードバックを提供することです。スタブで作業する場合、必要なアプリケーションは、アプリケーションが直接使用するアプリケーションだけです。次の図は、スタブとアプリケーションの関連を示しています。
Spring Cloud Contract を使用すると、使用するスタブが呼び出したサービスによって作成されたという確信が得られます。また、使えるということは、製作者側でテストされたということになります。つまり、これらのスタブは信頼できます。
目的
Spring Cloud Contract の主な目的は次のとおりです。
HTTP およびメッセージングスタブ (クライアントの開発時に使用される) が実際のサーバー側の実装とまったく同じことを行うようにするため。
ATDD (受け入れテスト駆動開発) 手法とマイクロサービスアーキテクチャスタイルを推進します。
契約の変更を公開し、双方ですぐに確認できる方法を提供します。
サーバー側で使用するボイラープレートテストコードを生成します。
デフォルトでは、Spring Cloud Contract は HTTP サーバースタブとしてワイヤーモック (英語) と統合されます。
Spring Cloud Contract の目的は、契約書にビジネス機能を書き始めることではありません。不正チェックのビジネスユースケースがあると仮定します。ユーザーが 100 の異なる理由で詐欺師になる可能性がある場合、肯定的な場合と否定的な場合の 2 つの契約を作成すると想定します。契約 テストは、完全な動作をシミュレートするためではなく、アプリケーション間の契約をテストするために使用されます。 |
契約とは何ですか ?
サービスの利用者として、正確に何を達成したいのかを定義する必要があります。期待を明確にする必要があります。こそ契約書を書くのです。言い換えれば、契約は、API またはメッセージ通信がどのようにあるべきかについての合意です。次の例を考えてみましょう。
クライアント企業の ID と当社からの借入希望額を含むリクエストを送信するとします。また、PUT
メソッドを使用して、これを /fraudcheck
URL に送信したいと考えています。次のリストは、Groovy と YAML の両方でクライアントを詐欺としてマークする必要があるかどうかをチェックする契約を示しています。
groovy
org.springframework.cloud.contract.spec.Contract.make {
request { // (1)
method 'PUT' // (2)
url '/fraudcheck' // (3)
body([ // (4)
"client.id": $(regex('[0-9]{10}')),
loanAmount : 99999
])
headers { // (5)
contentType('application/json')
}
}
response { // (6)
status OK() // (7)
body([ // (8)
fraudCheckStatus : "FRAUD",
"rejection.reason": "Amount too high"
])
headers { // (9)
contentType('application/json')
}
}
}
/*
From the Consumer perspective, when shooting a request in the integration test:
(1) - If the consumer sends a request
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `client.id` that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the response will be sent with
(7) - status equal `200`
(8) - and JSON body equal to
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` equal to `application/json`
From the Producer perspective, in the autogenerated producer-side test:
(1) - A request will be sent to the producer
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
* has a field `client.id` that will have a generated value that matches a regular expression `[0-9]{10}`
* has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the test will assert if the response has been sent with
(7) - status equal `200`
(8) - and JSON body equal to
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` matching `application/json.*`
*/
request: # (1)
method: PUT # (2)
url: /yamlfraudcheck # (3)
body: # (4)
"client.id": 1234567890
loanAmount: 99999
headers: # (5)
Content-Type: application/json
matchers:
body:
- path: $.['client.id'] # (6)
type: by_regex
value: "[0-9]{10}"
response: # (7)
status: 200 # (8)
body: # (9)
fraudCheckStatus: "FRAUD"
"rejection.reason": "Amount too high"
headers: # (10)
Content-Type: application/json
#From the Consumer perspective, when shooting a request in the integration test:
#
#(1) - If the consumer sends a request
#(2) - With the "PUT" method
#(3) - to the URL "/yamlfraudcheck"
#(4) - with the JSON body that
# * has a field `client.id`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(6) - and a `client.id` json entry matches the regular expression `[0-9]{10}`
#(7) - then the response will be sent with
#(8) - status equal `200`
#(9) - and JSON body equal to
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
#(10) - with header `Content-Type` equal to `application/json`
#
#From the Producer perspective, in the autogenerated producer-side test:
#
#(1) - A request will be sent to the producer
#(2) - With the "PUT" method
#(3) - to the URL "/yamlfraudcheck"
#(4) - with the JSON body that
# * has a field `client.id` `1234567890`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(7) - then the test will assert if the response has been sent with
#(8) - status equal `200`
#(9) - and JSON body equal to
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
#(10) - with header `Content-Type` equal to `application/json`
契約は信頼できるソースから得られることが期待されます。信頼できない場所からの契約をダウンロードしたり、操作したりしないでください。 |