背景
在現(xiàn)代的開發(fā)模式中,基于微服務(wù)的開發(fā)模式越來越常見,但是隨著項(xiàng)目規(guī)模的擴(kuò)大,服務(wù)與服務(wù)之間的依賴越來越密切,當(dāng)不同的開發(fā)團(tuán)隊(duì)去開發(fā)不同的服務(wù)時(shí),服務(wù)的提供者的變動(dòng)會(huì)影響到眾多消費(fèi)它的消費(fèi)者,為了保證系統(tǒng)的正確性和一致性,這將需要大量的溝通成本和代碼修改的時(shí)間成本。
之前遇到的某個(gè)客戶內(nèi)部就是因?yàn)榉?wù)與服務(wù)之間依賴過多,且存在各種的物理依賴,再加上其他種種原因,使得在集成測(cè)試時(shí)bug激增。對(duì)于他們而言集成測(cè)試需要依賴于各個(gè)服務(wù)版本的一致性以及真實(shí)的物理環(huán)境,因此他們的集成測(cè)試通常需要用上幾個(gè)小時(shí)才可以完成,這就使得整體的效率大大縮減。除此之外,在集成測(cè)試中發(fā)現(xiàn)的問題也會(huì)使得他們花很長(zhǎng)的時(shí)間去定位到問題所在。
相似的問題在平時(shí)的開發(fā)過程中也是經(jīng)常遇到,由于依賴方的接口變更導(dǎo)致在系統(tǒng)集成時(shí)頻頻出錯(cuò),整體的代碼又不得不再加修改,這就使得開發(fā)的進(jìn)度遲遲無法向前推進(jìn)。
為了解決這類的問題,契約測(cè)試應(yīng)運(yùn)而生。契約測(cè)試不是一個(gè)新鮮東西,但在實(shí)際項(xiàng)目經(jīng)歷中發(fā)現(xiàn)用好契約測(cè)試真的會(huì)大大增強(qiáng)開發(fā)的效率,因此寫下這篇文章來簡(jiǎn)單總結(jié)一下契約測(cè)試的一些內(nèi)容。
首先什么是契約測(cè)試
契約測(cè)試是一個(gè)為確保兩個(gè)獨(dú)立的系統(tǒng)或者微服務(wù)能夠兼容并可以相互通信的一個(gè)方法,契約測(cè)試分為兩種,一種是服務(wù)提供者驅(qū)動(dòng)的,另一種是消費(fèi)者驅(qū)動(dòng)的。如下圖所示,左側(cè)是一個(gè)服務(wù)的消費(fèi)者,右側(cè)是一個(gè)服務(wù)提供者,消費(fèi)者調(diào)用提供者的接口并消費(fèi)數(shù)據(jù)的交互過程會(huì)被記錄成一份契約,在契約中包含了服務(wù)的提供者和消費(fèi)者是誰,以及消費(fèi)者對(duì)服務(wù)的提供者的期望(如請(qǐng)求的參數(shù)和返回的結(jié)果)。服務(wù)的提供者會(huì)根據(jù)這份契約去反復(fù)驗(yàn)證自己是否能夠滿足消費(fèi)者的需求,這也就是所謂的消費(fèi)者驅(qū)動(dòng)。
契約測(cè)試主要是為了驗(yàn)證服務(wù)層提供的數(shù)據(jù)是否能夠消費(fèi)者正常使用,它不會(huì)深入去測(cè)試服務(wù)的行為,而只是專注于測(cè)試服務(wù)的輸入與輸出,因此相比于沉重的集成測(cè)試而言,契約測(cè)試會(huì)更加的輕巧,快速。契約測(cè)試形式上類似于API級(jí)別的UT,但其本質(zhì)上還是個(gè)集成測(cè)試,比API測(cè)試在金字塔的位置更靠頂端,所以容易導(dǎo)致契約測(cè)試的數(shù)量增加和不穩(wěn)定性增加。
契約測(cè)試具體是如何實(shí)踐的
接下來我們分別從代碼和流水線設(shè)計(jì)兩方面來闡述一下具體的契約測(cè)試的實(shí)踐:
代碼層面:
為了完成契約測(cè)試,我們可以借助一個(gè)叫pact的工具。pact是一個(gè)代碼優(yōu)先的用來支持契約測(cè)試的一個(gè)工具,它目前支持java,python,go等主流的開發(fā)語(yǔ)言。
Pact中的一些基本概念:
- Contract: 契約文件,在Pact中也叫做pact,可以保存在本地,也可存在broker中?
- Provider: 真正運(yùn)行的生產(chǎn)者服務(wù)?
- Consumer: 接收生產(chǎn)者發(fā)出的數(shù)據(jù)?
在pact中,consumer和provider分別做了不同的事:
Consumer端:
consumer端會(huì)做這么幾件事:
- 首先使用pact dsl定義它消費(fèi)的接口的request和response,并注冊(cè)到mock server中?
- 然后consumer端的測(cè)試會(huì)發(fā)送一個(gè)真實(shí)的請(qǐng)求到pact起的一個(gè)本地的mock server?
- 接著pact會(huì)去對(duì)比實(shí)際的request和expected request 是否一致,如果一致則返回expected response?
- 最后consumer會(huì)去確認(rèn)這個(gè)返回值是否正確 上面所有步驟都pass后,整個(gè)的consumer測(cè)的pact測(cè)試才算結(jié)束,此時(shí)consumer定下的契約會(huì)被發(fā)布到一個(gè)叫pact broker的地方進(jìn)行契約的統(tǒng)一管理。?
Pact broker是pact提供的一個(gè)專門用來統(tǒng)一管理契約的一個(gè)服務(wù),在這個(gè)服務(wù)中,開發(fā)者們可以清晰的看到所有的服務(wù)提供者和消費(fèi)者的詳細(xì)信息。
總的來說,cousumer端的主要功能是生成契約(文件的載體),驗(yàn)證request和response的工作是可選的,借由consumer端的集成測(cè)試的形式,確保生成的契約的確是consumer真正期望的,通俗來講,就是“測(cè)試測(cè)試的測(cè)試”。
Provider端:
在provider端,pact會(huì)mock出一個(gè)consumer并發(fā)送請(qǐng)求給provider端真實(shí)運(yùn)行著的進(jìn)程,provider在接受到請(qǐng)求后會(huì)根據(jù)自己的代碼實(shí)現(xiàn)將真實(shí)的response返回給pact,接著pact會(huì)拿著這個(gè)response去和pact broker上獲取到之前consumer定義的契約并進(jìn)行比對(duì),如果provider能夠滿足契約,則驗(yàn)證通過。
當(dāng)consumer和provider的測(cè)試都通過后,產(chǎn)品則就可以被部署到指定環(huán)境了。
以上是消費(fèi)者驅(qū)動(dòng)的一個(gè)實(shí)踐方式,消費(fèi)者驅(qū)動(dòng)的契約測(cè)試主要適用于以下場(chǎng)景:
- 消費(fèi)者和提供者都是可控的?
- 消費(fèi)者的需求變動(dòng)能夠變成提供者的需求?
- 消費(fèi)者數(shù)量不是很多,作為提供方能夠管理的過來?
符合以上的條件的場(chǎng)景下,比較適合使用消費(fèi)者驅(qū)動(dòng)的契約測(cè)試。消費(fèi)者驅(qū)動(dòng)的背景下,服務(wù)提供方可以基于消費(fèi)者提出的契約快速做出反饋。
然而,在實(shí)際的情況可能不是這么美好,之前遇到的客戶,他們內(nèi)部的部分情況恰恰違背了以上的場(chǎng)景。他們的產(chǎn)品極度依賴著一些外部的底層依賴,且底層的依賴變動(dòng)頻率較高,這使得他們會(huì)頻頻的在集成測(cè)試時(shí)發(fā)現(xiàn)底層已經(jīng)發(fā)生了變動(dòng)。在這種情景下,提供者驅(qū)動(dòng)的契約測(cè)試更加適合。由服務(wù)的提供方來約定契約,然后眾多的消費(fèi)者去滿足契約,當(dāng)提供方發(fā)生變動(dòng)時(shí),消費(fèi)方能夠及時(shí)感知到并快速反饋。整體的實(shí)踐流程只需將上方的consumer者和provider的操作進(jìn)行轉(zhuǎn)置即可。
換句話說,消費(fèi)者驅(qū)動(dòng)和提供者驅(qū)動(dòng)的區(qū)別在于誰去響應(yīng)契約的變化。就如上方提到的,外部的提供者依賴是不可控的情況下,提供者驅(qū)動(dòng)的模式會(huì)更加合適,相反則是消費(fèi)者驅(qū)動(dòng)的模式。
流水線的設(shè)計(jì)
當(dāng)選擇消費(fèi)者驅(qū)動(dòng)的契約測(cè)試策略時(shí),作為一個(gè)consumer,它要做的就是去發(fā)布契約,告訴provider它的需求。那么作為provider,它就需要去檢查自己的實(shí)現(xiàn)是否能夠滿足consumer的需求,那么當(dāng)它的實(shí)現(xiàn)無法滿足契約時(shí),則此時(shí)的流水線契約測(cè)試階段就應(yīng)該顯示fail,并告知對(duì)應(yīng)的provider,讓其快速做出修正 。如圖所示,當(dāng)consumer發(fā)布了新版本的契約,這將導(dǎo)致provider端的流水線fail,那么此時(shí)provider就會(huì)得知他們需要根據(jù)新的契約來修改實(shí)現(xiàn)了。
而和消費(fèi)者驅(qū)動(dòng)相反,提供者驅(qū)動(dòng)的設(shè)計(jì)則是當(dāng)provider發(fā)布了一個(gè)新的契約之后consumer側(cè)的流水線會(huì)變紅,直到consumer將他們的代碼根據(jù)新的契約修正后才可以進(jìn)入后面的集成測(cè)試。
契約測(cè)試帶來的好處
(1) 測(cè)試的速度快,無需依賴多個(gè)系統(tǒng)之間的交互
細(xì)心的同學(xué)通過上面的描述會(huì)發(fā)現(xiàn),在契約測(cè)試時(shí)服務(wù)的依賴方式不需要被真實(shí)調(diào)用的,契約測(cè)試通過mock依賴的方式來模擬依賴方的行為,這就使得測(cè)試的速度得以大大提升
(2) 可以并行開發(fā)
由于mock的存在,使得服務(wù)的消費(fèi)方和提供方可以根據(jù)事先定義好的契約進(jìn)行并行開發(fā)
(3) 發(fā)現(xiàn)問題后可以快速定位到問題:
因?yàn)閱栴}只會(huì)出現(xiàn)在當(dāng)前測(cè)試的服務(wù)或者組件中,你甚至可以確切的知道是哪個(gè)api測(cè)試fail了
(4) 在確定完契約之后,開發(fā)人員可以在本地就可以進(jìn)行測(cè)試,無需將代碼推至遠(yuǎn)端
(5) 測(cè)試前移
把本來要通過集成測(cè)試才能驗(yàn)證的工作化作單元測(cè)試和接口測(cè)試,用更輕量的方式快速進(jìn)行驗(yàn)證,更早的發(fā)現(xiàn)問題使得后續(xù)的測(cè)試更加快速
契約測(cè)試和其他測(cè)試的對(duì)比
總結(jié)
總體來說,契約測(cè)試是一個(gè)介于單元測(cè)試和集成測(cè)試的一個(gè)階段,他關(guān)注的細(xì)粒度比單元測(cè)試更粗,但是又無法取代集成測(cè)試。尤其是當(dāng)你的產(chǎn)品對(duì)環(huán)境依賴特別大的時(shí)候,集成測(cè)試還是必不可少的一部分,契約測(cè)試的存在只是為了讓你在開發(fā)過程中的聯(lián)調(diào)更加快速,集成時(shí)問題更少。?