提高微服務測試效率:消費者驅動契約測試

本文由公衆號EAWorld編譯發表,轉載需註明出處。html


做者:EAWorld
編譯:白小白 

全文4977字,閱讀約須要8分鐘
 node

概述:


在軟件工程的世界裏,咱們常常面臨變化。微服務不只改變了軟件的體系結構,並且改變了團隊的組織方式和協做方式。

相對於單體式應用,微服務有其優點,同時,也有引入後所新產生的問題,測試就是問題之一。

在這篇文章中,咱們想概述一下測試如何在微服務的新世界中發生變化。咱們還將介紹消費者驅動的契約測試的細節和支持它的框架。

爲了較爲全面的闡述CDCT的概念,本文翻譯、引用、和綜合了多篇相關文章的內容,相關連接附後。
 web

目錄:


1、單元測試

2、端到端(系統)測試

3、集成測試

4、使用消費者驅動契約測試(CDCT)

5、總結
 spring

1、單元測試


當咱們談到微服務時,咱們還應該進行單元測試嗎?答案是確定的,單元測試已經證實是一種可靠的、快速的,也不是那麼昂貴的方法來測試業務邏輯的有效性。可是單元測試僅僅保證服務提供者或者服務消費者某一方的代碼是有效的或者功能是正常的,而不能保證服務之間的交互是有效的。而這偏偏是微服務的核心應用場景之一。
 數據庫

2、端到端(系統)測試


當咱們談到微服務時,咱們還應該進行端到端的測試嗎?是的,進行端到端測試是很重要的,可是當咱們談到微服務時,爲了執行端到端的測試,須要部署從服務消費者到服務提供者之間全部環節的相關調用,複雜程度可能會很是高。
 json

3、集成測試


測試兩個服務(提供者和消費者)之間的交互的傳統方法是使用集成測試。這樣作的目的是在某些集成環境中同時運行消費者服務和提供者服務,並檢查它們是否按預期進行交互。這種類型的測試模擬了服務在生產環境中的行爲,所以在理論上集成測試是有意義的。然而,這種方法存在一些問題。

首先,集成測試一般比較慢。它們須要設置集成環境,啓動消費者和提供者服務並初始化它們的依賴關係。起初,這彷佛不是一個問題,可是隨着集成測試的數量開始增長,構建過程變得愈來愈慢。在微服務體系結構中尤爲如此。在每一對交互的微服務之間進行集成測試是不合適的。

集成測試的另外一個問題是它們很脆弱。有時,它們會由於與服務自己無關的緣由而失敗,可能存在網絡問題或數據庫之類的外部依賴關係。而意味着失敗的集成測試並不必定意味着代碼存在問題。

集成測試的另外一個問題是定位困難。即便因爲消費者和提供者服務之間的實際集成問題而致使集成測試失敗,很難肯定問題的所在:這是消費者服務的錯誤嗎?仍是提供者的服務?仍是二者兼而有之?

集成測試增長了額外的團隊開銷。集成測試主要由QA團隊執行,而不是由開發人員本身執行,這意味着在出現問題時,團隊之間須要額外的開銷。這也致使了一個問題:誰更適合測試兩個服務之間的集成點:QA團隊?仍是服務的實際開發人員?在到達QA以前,清楚地知道兩個服務在開發時是否正確地交互,將爲咱們節省大量的時間和開銷。
 api

4、使用消費者驅動契約測試(CDCT)


雖然三種方式各有利弊,但與集成測試及端到端測試相比,單元測試相對來講是健壯、可靠的,它們工做速度快,而且很是具體地告訴咱們問題在哪裏。若是能夠更加有效的測試方法改進單元測試來驗證服務間交互,確定會改善咱們的開發、測試和部署體驗。

消費者驅動契約測試(Consumer-Driven Contracts Testing)背後的理念是定義每一個服務消費者與提供者之間的契約,而後根據該契約對消費者和提供者進行獨立測試,以驗證他們是否符合契約約定的事項。

爲了更好地理解,咱們將使用如下示例模型來描述這一微服務測試方法背後的概念。



在上圖中,咱們能夠看到兩個微服務經過REST相互通訊。第一個服務是消費者(Consumer)的角色,第二個是提供者(Provider)的角色。

當服務提供者不發生變化的狀況下,好比咱們經過Mock模擬服務提供者的相關反饋,相關測試是能夠經過的。



可是,若是是在生產環境中,測試時模擬的服務反饋極可能跟不上服務提供者的變化,好比服務提供者更改了服務的數據格式,從「名字,姓名「到」人名「。集成測試將沒法捕捉到這個問題,由於它們是針對過期版本的提供程序運行的,此時,就會發生以下的狀況。



消費者驅動契約的理念是將服務消費者和提供者之間的互動正式化。服務消費者建立一個契約,它是服務消費者和提供者之間就他們之間將要發生的交互達成的協議。或者換句話說,提出服務消費者對提供者的指望。一旦提供者就契約達成協議,消費者和提供者均可以獲取契約的副本,並使用測試來驗證它們的相應實現沒有違反契約。



消費者驅動的契約測試,一般實現方式以下:

1. 選擇合適的場景,定義消費者的請求和指望的響應。

2. 使用Mock機制,爲消費者提供模擬的提供者以及指望的響應。

3. 記錄消費者發送的請求、提供者提供的響應以及關於場景的其它元數據,並將其記錄爲當前場景的契約。

4. 模擬消費者,向真正的提供者模擬發送請求。

5. 驗證提供者提供的契約是否和以前記錄的契約同樣。

這種新的測試方法的優勢是它們基本上是添加了交互條件的單元測試:它們能夠在本地獨立運行,並且速度快、可靠。但這其實與Mock方式模擬的好處至關,事實上,CDCT所帶來的優點遠非如此。

優點1:下降接口變化帶來的服務消費者風險

CDCT契約的發起方是服務消費者,由服務消費者定義本身所須要的反饋信息,所以,能夠保證服務消費者老是可以得到本身所需的反饋。而不論服務提供者一方發生了什麼變化。以CDCT測試框架PACT爲例。



服務消費者經過創建模擬提供者的Mock,能夠對請求、響應和相關信息記錄下來,成爲一個Pact文件。這個文件就是消費者與提供者之間的契約。在這個過程當中,服務提供者無需進行任何操做。



接下來,在服務提供者一端,將經過模擬消費者的Mock對Pact文件進行回放,要求服務提供者針對該契約作出正確的響應。經過這樣的的過程,完成一次完整的從服務消費者向服務提供者的驅動過程。



當服務提供者須要對接口作出變動時,仍舊須要遵循契約的要求,以反饋正確的結果,這樣,就能夠保證服務消費者老是獲得正確的信息而不論服務提供者的接口發生怎樣的變化。除非消費者端主動的從新訂立契約。

優點2:解耦開發團隊,下降測試成本,解放生產力

當服務消費者和服務提供者以契約爲中介造成解耦的時候,相關的技術團隊也所以造成了解耦,而不須要必定針對一個端到端的測試場景來進行配合。

這裏咱們引入兩個技術團隊進行相關的測試。左側的是服務消費者,須要經過ID查詢用戶的郵件地址,右側的是服務提供者,負責反饋正確的郵件地址信息。



在服務消費者和提供者之間創建一個契約,咱們稱之爲TEST,來要求服務提供者根據ID反饋正確的EMAIL。



服務消費者能夠經過運行TEST測試來了解本身可否得到正確的信息,但事實上,這並無必要,由於只有當服務提供者一方發生服務接口的變動時,纔會影響契約的效力,因此正確的作法是,只須要在服務提供者一方來進行對契約的驗證測試便可。



這樣,服務消費者將經過契約來驅動服務提供者完成既定的功能反饋,當雙方對此過程協調一致,運轉正常以後,服務提供者將再也不須要服務消費者來發布任何契約的變動,就能夠單獨的依賴契約發現代碼的缺陷。而服務消費者技術團隊,就能夠專一於自己的事情,甚至於去支持其餘的項目內容。



應用場景舉例:第三方API的集成測試

在現實場景中,一方面企業內部會有諸多的遺留系統API,另外一方面也同時會有不少狀況須要調用外部的API,好比谷歌地圖,這些狀況下API並不受咱們的掌控,即便提交一些反饋,相關變動也可能以數週或者數月爲單位,甚至對於遺留系統來講,相關的供應商都已經不復存在。

對於應用將對這類API進行集成的場景,此時,應用是消費者端,而API是服務提供端,咱們能夠有三種處理方式:

一、消費者端手動檢查:經過手動檢查應用程序是否作了它應該作的事情以及是否使用了來自API的正確值來確保應用程序仍然工做。

二、服務者端真實調用:首先確認API被正確集成,測試的時候直接調用API來檢查相關功能是否正確,這將涉及網絡帶來的測試速度的影響,以及調用費用的消耗,畢竟每一次調用都不是免費的。

三、記錄服務端反饋,並在代碼庫中回放:在這種狀況下,僅須要調用一次API,並將相關反饋記錄爲JSON文件,從而解決了網絡和費用問題,但仍舊沒法繞開一旦服務接口發生變化帶來的影響。

引入 CDCT能夠緩解這個問題。但顯然咱們不能將契約發佈給Google Maps API或咱們遺留的CRM系統,並迫使他們遵照。這些提供者可能既不關心也不具有支持CDCT的工具。所以,乍一看,爲第三方API使用CDCT彷佛很奇怪。

咱們能夠作的是在自動化測試期間,建立另外一個服務,做爲谷歌API的替代品。該服務將保存從實際API中定義所需字段的契約。咱們稱這些服務爲代理。它們從不代理HTTP請求,而是在自動化測試期間充當谷歌API和應用之間的中間角色。代理將有兩個目標:

1.確保API按預期響應,就像在實際調用真實的谷歌API同樣。

2.向服務消費者提供契約文件,以供回放,相似於一個JSON響應文件。

讓咱們舉個例子,咱們要展現從德國斯圖加特到柏林須要多長時間。使用Google距離矩陣API 咱們進行以下的調用:

http https://maps.googleapis.com/maps/api/distancematrix/json \
origins==Berlin destinations==Stuttgart


調用結果是

{
"destination_addresses" : [ "Berlin, Germany" ],
"origin_addresses" : [ "Stuttgart, Germany" ],
"rows" : [
{
"elements" : [
{
"distance" : {
"text" : "636 km",
"value" : 635736
},
"duration" : {
"text" : "6 hours 18 mins",
"value" : 22651
},
"status" : "OK"
}
]

},
"status" : "OK" 


經過這樣的請求調用,咱們瞭解到,從柏林開車到斯圖加特須要大約6小時18分鐘的時間。這個時間是經過22651來換算取得。這是以秒爲單位的持續時間。咱們的服務消費者,例如Android應用程序,可能想決定他們想如何爲用戶對這個值作格式化。所以咱們應該確保這個經行時間字段包含在響應中,也就是說,針對這個值作契約上的約定。

以 Spring Cloud Contract 的 Groovy DSL 爲例,咱們能夠定義以下的契約:

org.springframework.cloud.contract.spec.Contract.make {
request {
method GET()
url("/maps/api/distancematrix/json") {
queryParameters {
parameter 'origins': 'Berlin'
parameter 'destinations': 'Stuttgart'
}
}
}
response {
status 200
body([
rows  : [[
elements: [[
duration: [
value: 22651
]
]]
]],
])
}
}

能夠看到,相對於此前的完整反饋,契約只包含咱們關心的部分響應和用於建立預期響應的所應發出的請求。框架將能夠自動生成如下的測試代碼和相關字段 的斷言。

@Test
public void validate_shouldProvideDistanceBetweenTwoCities() {
// when:
Response response = webTarget
.path("/maps/api/distancematrix/json")
.queryParam("origins", "Berlin")
.queryParam("destinations", "Stuttgart")
.request()
.method("GET");
String responseAsString = response.readEntity(String.class);
// then:
assertThat(response.getStatus()).isEqualTo(200); 
// and:
DocumentContext parsedJson = JsonPath.parse(responseAsString);
assertThatJson(parsedJson).array("['rows']")
.array("['elements']").field("['duration']")
.field("['value']").isEqualTo(22651);


若是咱們在請求中提供指定的參數(when部分),預期應該得到指定的響應(then部分)。生成的契約測試不須要咱們編寫任何實現代碼就能夠經過。

而且在測試運行以後,咱們會獲得一些JSON文件做爲存根,相似PACT的契約文件,保存在本地用於應用測試。

若是實際的谷歌API服務調整了兩地的行經時間由25561改成25562,上述的代碼可能就並不適用了。咱們須要將生成的斷言修改以下的內容:

assertThatJson(parsedJson).array("['rows']")
.array("['elements']").field("['duration']")
.field("['value']").matches("\\d+");

這樣,在實際調用過程當中,即便谷歌API反饋的是12345,服務消費方也不會崩潰。

此外要讓測試命中存根而不是真正的API,咱們須要配置以下的服務映射。

stubrunner:
ids: 'co.hodler:scdcproxy:+:stubs'
stubsMode: LOCAL
ids-to-service-ids:
scdcproxy: google-distance-service

經過使用CDCT技術,咱們確保了

該API的行爲與咱們預期的同樣。

除了代理項目以外,咱們的測試不調用真正的API。

咱們確保預期的響應和實際的響應之間沒有不匹配。

主流框架介紹

可以完成CDCT任務的框架有Janus\Pact\Pacto\Spring Cloud Contract等,網上能夠找到比較多資料的是PACT和Spring Cloud Contract。

PACT(https://docs.pact.io/)

其官網的說明是這樣的:

PACT是一種契約測試工具。契約測試是一種確保服務(例如API提供程序和客戶端)可以相互通訊的方法。若是沒有契約測試,瞭解服務能夠通訊的惟一方法就是使用昂貴而脆弱的集成測試。你是否放火燒了你的房子來測試你的煙霧報警器?不,你用測試按鈕來測試它和你耳朵之間的合同。PACT爲您的代碼提供了測試按鈕,容許您安全地確認您的應用程序將一塊兒工做,而沒必要先部署這個世界。

Pact是一個開源框架,最先是由澳洲最大的房地產信息提供商REA Group的開發者及諮詢師們共同創造。REA Group的開發團隊很早便在項目中使用了微服務架構,並在團隊中對於敏捷和測試的重要性早已造成共識,所以設計出這樣的優秀框架並應用於平常工做中也是十分天然。

Pact工具於2013年開始開源,發展到今天已然造成了一個小的生態圈,包括各類語言(Ruby/Java/.NET/JavaScript/Go/Scala/Groovy...)下的Pact實現,契約文件共享工具Pact Broker等。Pact的用戶已經遍佈包括RedHat、IBM、Accenture等在內的若干知名公司,Pact已是事實上的契約測試方面的業界標準。

Spring Cloud Contract(https://cloud.spring.io/spring-cloud-contract/)

Spring Cloud Contract是一套完整的解決方案,幫助用戶成功地實現消費者驅動的契約方法。目前,Spring Cloud Contract的主體是Spring Cloud Contract Verifier項目。

Spring Cloud Contract Verifier是一個工具,它支持基於JVM的應用程序的消費者驅動契約(CDC)開發。用Groovy或YAML編寫契約定義語言(DSL)。

Spring Cloud Contract Verifier將TDD提高到軟件體系結構的級別。
 安全

5、總結


消費者驅動的契約測試,關鍵理念在於兩個方面:

一是,經過提供中介契約,造成了服務消費者和服務提供者之間的解耦

二是,由消費者出發發佈契約的方式,確保服務消費者的價值得以優先實現

從而帶來的好處是:

一是服務提供端的接口變化不會對服務消費端產生影響

二是下降了傳統的集成測試以及端到端測試過程當中的昂貴成本。

三是快速反饋、獨立部署、下降複雜度,更快的開發速度和更短的迭代時間。


本文直接引用或者參考了以下的文章來源:
1.https://blog.csdn.net/wzxq123/article/details/80219772

2.https://techbeacon.com/end-end-vs-contract-based-testing-how-choose

3.https://techblog.poppulo.com/why-should-you-use-consumer-driven-contracts-for-microservices-integration-tests/

4.https://dzone.com/articles/consumer-driven-contracts-with-pact-feign-and-spri

5.http://www.lor.beer/a-guide-to-testing-microservices/

6.http://www.lor.beer/how-to-consumer-driven-contract-tests/

7.http://hecodes.com/2016/10/better-testing-microservices-using-consumer-driven-contracts-node-js/

8.https://blog.novatec-gmbh.de/introduction-microservices-testing-consumer-driven-contract-testing-pact/

9.https://medium.com/@axelhodler/integration-tests-for-third-party-apis-dab67c52e352

10.https://www.cnblogs.com/Wolfmanlq/p/7966408.html


關於EAWorld:微服務,DevOps,數據治理,移動架構原創技術分享,長按二維碼關注


 網絡

相關文章
相關標籤/搜索