一文掌握契約測(cè)試
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)因?yàn)槲⒎?wù)的流行而再次火了起來(lái),契約測(cè)試也是一樣。
為了在微服務(wù)開(kāi)發(fā)模式下跨團(tuán)隊(duì)協(xié)調(diào)更有效率,提升持續(xù)集成流水線(xiàn)自動(dòng)化水平,契約測(cè)試有效彌補(bǔ)了集成測(cè)試的不足,強(qiáng)勢(shì)C位出鏡。
本文將通過(guò)逐步介紹契約測(cè)試是什么,怎么做,有哪些工具,有哪些最佳實(shí)踐和經(jīng)驗(yàn)教訓(xùn),帶您一起徹底掌握契約測(cè)試。
什么是契約測(cè)試
契約測(cè)試(Contract testing)是一種測(cè)試技術(shù),它通過(guò)以隔離檢查集成點(diǎn)上的每個(gè)應(yīng)用的方式,確保應(yīng)用發(fā)送或接收的消息符合調(diào)用雙方共識(shí),并允許隨著時(shí)間的推移進(jìn)行演化。
為什么要做契約測(cè)試
契約測(cè)試主要解決在存在溝通邊界情況下,測(cè)試替身(Test Double)與生產(chǎn)代碼表現(xiàn)可能不一致的問(wèn)題。在契約測(cè)試中,契約由代碼生成,保持與現(xiàn)實(shí)同步,而且應(yīng)用可以獨(dú)立于其它應(yīng)用而僅基于契約進(jìn)行快速測(cè)試。
由于集成測(cè)試容易受到網(wǎng)絡(luò)緩慢或不可靠,以及服務(wù)不可靠等因素的影響而運(yùn)行緩慢或失敗,所以通常會(huì)引入測(cè)試替身來(lái)代替真實(shí)外部服務(wù),以快速完成覆蓋度更廣的測(cè)試,讓測(cè)試真正起到作用。
但是,這樣做的同時(shí),帶來(lái)了測(cè)試替身是否可以持續(xù)準(zhǔn)確表示外部服務(wù)的問(wèn)題。于是,需要單獨(dú)補(bǔ)充運(yùn)行一組契約測(cè)試,來(lái)檢查所有對(duì)測(cè)試替身調(diào)用的返回結(jié)果總是與對(duì)外部服務(wù)調(diào)用的返回結(jié)果相同。
契約測(cè)試的定位
金字塔模型是構(gòu)建健康、快速、可維護(hù)測(cè)試集的成熟理論。
契約測(cè)試適合歸屬于服務(wù)測(cè)試(Service Tests)層,因?yàn)樗鼈儓?zhí)行得很快,也不需要和外部服務(wù)集成來(lái)運(yùn)行。契約測(cè)試運(yùn)行于發(fā)布版本之前,為成功集成提供信心。
契約測(cè)試的價(jià)值
眾所周知,越是在項(xiàng)目生命周期的后期發(fā)現(xiàn)Bug,其修復(fù)的成本就越高。
不同于端到端(E2E)測(cè)試,契約測(cè)試可以在開(kāi)發(fā)人員推送代碼之前運(yùn)行,在開(kāi)發(fā)階段提早發(fā)現(xiàn)問(wèn)題。
契約測(cè)試還有很多端到端測(cè)試不具備的好處:
- 不需要調(diào)用其它組件,運(yùn)行得很快。
- 編寫(xiě)測(cè)試不需要了解系統(tǒng)全貌,更容易維護(hù)。
- 問(wèn)題只存在于被測(cè)試組件中,更容易調(diào)試和修復(fù)。
- 極易反復(fù)運(yùn)行。
- 每個(gè)組件獨(dú)立測(cè)試,不會(huì)引發(fā)流水線(xiàn)構(gòu)建時(shí)間大幅增長(zhǎng)。
引入契約測(cè)試,還會(huì)帶來(lái)如下福利:
- 在提供者API就緒之前就可以開(kāi)發(fā)消費(fèi)者應(yīng)用。
- 為提供者供應(yīng)準(zhǔn)確的需求
- 會(huì)收獲一組文檔化良好的用例,它們確切地顯示了如何使用提供者。
- 提供者對(duì)API變更更有信息,可以準(zhǔn)確知道使用者感興趣的字段,方便地移除未使用的字段,以及添加新的字段。
- 對(duì)提供者API進(jìn)行修改,可以立即看到會(huì)影響哪些使用者。
沒(méi)有兩個(gè)團(tuán)隊(duì)是完全一樣的,契約測(cè)試也不是萬(wàn)能的,關(guān)鍵要看契約測(cè)試可以為團(tuán)隊(duì)和項(xiàng)目帶來(lái)什么。
契約測(cè)試適合的場(chǎng)景
契約測(cè)試可以用于任何需要通信的兩個(gè)服務(wù),比如Web前端與后端API服務(wù)。
在微服務(wù)架構(gòu)體系中,因?yàn)榇嬖诟鄨F(tuán)隊(duì)獨(dú)立、服務(wù)間調(diào)用及服務(wù)單獨(dú)演進(jìn)的情形,契約測(cè)試有了更好更大的用武之地。良好的契約測(cè)試,使得開(kāi)發(fā)人員很容易避免版本地獄,是微服務(wù)開(kāi)發(fā)和部署的利器。
概念術(shù)語(yǔ)
契約測(cè)試主要涉及如下概念術(shù)語(yǔ):
- 消費(fèi)者(Consumer):對(duì)于調(diào)用,發(fā)起請(qǐng)求的一方。對(duì)于MQ,為接收消息的一方。
- 提供者(Provider):對(duì)于調(diào)用,響應(yīng)請(qǐng)求的一方。對(duì)于MQ,為生成消息的一方。
- 契約(Contract):消費(fèi)者和提供者之間的共識(shí),是一系列交互的集合。對(duì)于HTTP調(diào)用,包括描述消費(fèi)者向提供者發(fā)送什么的預(yù)期請(qǐng)求,以及描述消費(fèi)者希望提供者返回的最小期望響應(yīng)。對(duì)于消息交互,則描述消費(fèi)者希望得到的最小期望消息。
契約測(cè)試模式
契約測(cè)試分為消費(fèi)者驅(qū)動(dòng)(consumer-driven)和提供者驅(qū)動(dòng)(Provider-driven)兩種模式。
消費(fèi)者驅(qū)動(dòng)更具哲學(xué)意義,將API的消費(fèi)者置于設(shè)計(jì)過(guò)程的核心,來(lái)倡導(dǎo)更好的內(nèi)部微服務(wù)設(shè)計(jì)。該模式的優(yōu)點(diǎn)在于,只有消費(fèi)者正在使用的部分會(huì)得到測(cè)試,而提供者可以自由地更改消費(fèi)者不使用的任何其它部分,而不必破壞任何現(xiàn)有測(cè)試。
提供者驅(qū)動(dòng)思路較為常規(guī),更適合開(kāi)放數(shù)據(jù)或系統(tǒng)的場(chǎng)景。
無(wú)論采用哪種風(fēng)格,關(guān)鍵在于獲得契約測(cè)試的好處,實(shí)現(xiàn)引入契約測(cè)試的目的。
契約測(cè)試基本步驟
1、消費(fèi)者驅(qū)動(dòng)
消費(fèi)者驅(qū)動(dòng)的契約測(cè)試運(yùn)行步驟如下:
- 消費(fèi)者基于提供者的mock編寫(xiě)和執(zhí)行消費(fèi)者測(cè)試
- 消費(fèi)者方通過(guò)消費(fèi)者測(cè)試生成契約,并將契約共享給提供者
- 提供者根據(jù)契約編寫(xiě)測(cè)試
2、提供者驅(qū)動(dòng)
提供者驅(qū)動(dòng)模式由提供者定義契約并驅(qū)動(dòng)整個(gè)過(guò)程。
契約測(cè)試工具
流行的契約測(cè)試工具為:
- Pact:是一個(gè)命令行工具,反饋時(shí)間更短,有助于消費(fèi)者和生產(chǎn)者之間更好地溝通。
- Spring Cloud Contract:主要用于JVM環(huán)境,也容易擴(kuò)展到非JVM環(huán)境,主要適用于生產(chǎn)者驅(qū)動(dòng)的契約測(cè)試。
利用Pact進(jìn)行消費(fèi)者驅(qū)動(dòng)的測(cè)試
利用Pact進(jìn)行契約測(cè)試的整個(gè)流程示意如下。
1、消費(fèi)者生產(chǎn)代碼
2、為消費(fèi)者編寫(xiě)測(cè)試
3、生成契約文件
一旦上面的測(cè)試通過(guò)了,就會(huì)在 target/pacts 文件夾中生成一個(gè)pact契約文件,文件名稱(chēng)為user-consume-service-user-provider-service.json,文件內(nèi)容如下:
4、利用Broker共享契約
Pact Broker是一個(gè)用于共享消費(fèi)者驅(qū)動(dòng)的契約,并驗(yàn)證結(jié)果的應(yīng)用程序,對(duì)于Pact創(chuàng)建的契約做了優(yōu)化,但也可以用于任何可以序列化為JSON的契約。
Pact Broker既支持在云上使用,也可以在本地部署。
以下是一個(gè)利用Docker Compose來(lái)本地部署Pack Broker的描述文件。
以下是Pact Broker啟動(dòng)后的界面樣子。
接下來(lái)就可以使用pack-jvm的pactPublish命令將契約文件發(fā)布到Broker。
首先在build.gradle文件中添加需要的配置。
然后,運(yùn)行如下命令發(fā)布契約。
命令執(zhí)行成功后,即可在Pact Broker上看到已發(fā)布的契約。
同時(shí),可以在Broker上查看契約細(xì)節(jié)。
至此,消費(fèi)者方已完成契約創(chuàng)建、發(fā)布等全部工作。
5、提供者端驗(yàn)證
如下為提供者的生產(chǎn)代碼。
以下代碼用于提供者對(duì)契約的驗(yàn)證。
以下為測(cè)試運(yùn)行結(jié)果的樣子。
至此,已完成提供者測(cè)試,并證實(shí)了契約被正確履行。
在Broker上,可以看到契約被驗(yàn)證通過(guò)。
至此,消費(fèi)者、提供者一起完成了整個(gè)契約測(cè)試。
利用SCC進(jìn)行提供者驅(qū)動(dòng)的測(cè)試
利用Spring Cloud Contract進(jìn)行契約測(cè)試的過(guò)程示意如下。
SCC各組件相互關(guān)系描述如下。
1、提供者添加SCC依賴(lài)和maven插件
2、提供者定義契約
SCC允許通過(guò)groovy、yaml、代碼等多種方式定義契約。
契約中包含在body中定義的消息示例及在matcers中定義的字段匹配器等。
3、提供者驗(yàn)證契約
首先創(chuàng)建一個(gè)測(cè)試基類(lèi),以便使用命令來(lái)生成測(cè)試代碼。
測(cè)試基類(lèi)的作用是在每次測(cè)試運(yùn)行之前初始化提供者API和其他配置。
接下來(lái),框架將在maven插件被構(gòu)建過(guò)程調(diào)用后,基于契約生成測(cè)試代碼。
測(cè)試代碼將向提供者發(fā)送帶有契約中示例數(shù)據(jù)的請(qǐng)求,并解析響應(yīng),根據(jù)契約驗(yàn)證響應(yīng)。
4、存儲(chǔ)契約
提供者生成的契約jar文件,可以發(fā)布到Nexus、JFrog等構(gòu)建庫(kù)中存儲(chǔ),同時(shí)和其他maven構(gòu)建一樣,可以通過(guò)group Id、artifact id、version等識(shí)別和獲得。
5、消費(fèi)者驗(yàn)證契約
在消費(fèi)者端,SCC框架為其提供了一個(gè)Stub Runner,它可以獲取存根定義并將其注冊(cè)到WireMock服務(wù)器。而該Mock服務(wù)器將模擬測(cè)試用例提供者的API,以支持消費(fèi)者驗(yàn)證契約。
因此,首先需要為消費(fèi)者應(yīng)用添加stub runner的maven依賴(lài)。
以下為消費(fèi)者契約測(cè)試代碼。
其中@AutoConfigureStubRunner用于指定契約存根的maven組件信息,包括group id、artifact id和端口號(hào)。然后stub runner就可以從本地或遠(yuǎn)程存儲(chǔ)庫(kù)獲取存根定義。
如下測(cè)試代碼將調(diào)用端口為6565的mock服務(wù)器,并對(duì)響應(yīng)進(jìn)行斷言。
最佳實(shí)踐
契約測(cè)試有如下最佳實(shí)踐。
- 契約測(cè)試的關(guān)注點(diǎn)應(yīng)該是請(qǐng)求和響應(yīng)的消息,而不是其行為
- 契約測(cè)試應(yīng)該與數(shù)據(jù)無(wú)關(guān)
- 基于Broker將整個(gè)過(guò)程與CI集成
- pact用于契約測(cè)試,而不是功能測(cè)試
- 只針對(duì)那些一旦發(fā)生變化就會(huì)影響消費(fèi)者的事情進(jìn)行斷言
- 將最新的契約提供給提供者
經(jīng)驗(yàn)教訓(xùn)
1、讓所有人都上船
契約測(cè)試需要跨團(tuán)隊(duì)協(xié)作,盡快讓各方都加入進(jìn)來(lái),就模式和工具等各方面達(dá)成一致,否則很快就會(huì)遇到麻煩。
2、不要低估學(xué)習(xí)曲線(xiàn)
契約測(cè)試是一種新型的測(cè)試方法,即使擁有豐富的單元測(cè)試、集成測(cè)試等其它測(cè)試類(lèi)型的豐富經(jīng)驗(yàn),也并不能代表可以編寫(xiě)有價(jià)值、可維護(hù)的契約測(cè)試,真正的挑戰(zhàn)在于如何處理API和契約隨時(shí)間的變化。
3、溝通仍然是必要的
工具并不能代替彼此之間的交流,契約測(cè)試也是一樣,至少在初始階段。而在后期,消費(fèi)者更新契約之前,仍然有必要事先與提供者進(jìn)行討論。
4、Pact更好用
Spring Cloud Contract不太適合消費(fèi)者驅(qū)動(dòng)的契約測(cè)試,總是需要消費(fèi)者等待提供者完成相關(guān)工作,而Pact則不需要這種等待。
另外,SCC使用Groovy編寫(xiě)的契約需要手動(dòng)與消費(fèi)者代碼保持同步,而Pact的API則相當(dāng)成熟,會(huì)自動(dòng)化生成各種代碼和文件。同時(shí)SCC在提供者端提供的的設(shè)置和斷言選項(xiàng)較少。Pact的社區(qū)支持也相對(duì)較好。
Pact對(duì)多語(yǔ)言的支持也更好。
小技巧
可以使用swagger-diff工具比較兩個(gè)版本的API
swagger-diff工具可以用來(lái)比較兩個(gè)Swagger API規(guī)范,并將結(jié)果輸出到HTML或Markdown文件中。