新聞中心
一、什么是契約

創(chuàng)新互聯(lián)服務(wù)項目包括肇州網(wǎng)站建設(shè)、肇州網(wǎng)站制作、肇州網(wǎng)頁制作以及肇州網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,肇州網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到肇州省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
如果從契約產(chǎn)生的階段來說,現(xiàn)有資料表明最早要追溯到西周時期的《周恭王三年裘衛(wèi)典田契》,將契約文字刻寫在器皿上,就是為了使契文中規(guī)定的內(nèi)容得到多方承認(rèn)、信守,“萬年永寶用”。所以訂立契約的本身,就是為了要信守,就是對誠信關(guān)系的一種確立。誠信,是我國所固有的一種優(yōu)良傳統(tǒng),也是延續(xù)了幾千年的一種民族美德,在中國儒家的思想體系里,是倫理道德內(nèi)容中的一部分。
《現(xiàn)藏于臺北故宮博物院》
現(xiàn)實真的是那么美好嗎?小時候的價值觀教育未能改變社會的現(xiàn)狀,缺少契約精神的案例卻比比皆是。
那么,契約真的要消失了嗎?不盡然,在軟件測試領(lǐng)域,我們又重新拾起了契約這把利器。
二、發(fā)展歷程
接下來讓我們回顧一下契約測試的起源和發(fā)展歷程:
假設(shè)我們有這樣一個場景:A團隊負(fù)責(zé)開發(fā)API服務(wù),B團隊進行API調(diào)用消費服務(wù)。
為了保證API的正確性,我們會對外部系統(tǒng)的API進行測試(除非你100%相信外部系統(tǒng)永遠(yuǎn)正確和保持不變),這很可能就會導(dǎo)致一個問題,當(dāng)外部系統(tǒng)并不那么穩(wěn)定或者請求時間過長時,就會導(dǎo)致我們的測試效率很低,并且穩(wěn)定性下降。比如當(dāng)外部API掛掉導(dǎo)致測試失敗時,你并不能完全確信是API功能被更而改導(dǎo)致的失敗還是運行環(huán)境不穩(wěn)定導(dǎo)致的請求失敗。
最初,解決這個問題的方案是構(gòu)建測試替身(Test Double),通過模擬外部API的響應(yīng)行為來增強測試的穩(wěn)定性和反應(yīng)速度。實現(xiàn)手段是在測試環(huán)境中搭建一個模擬服務(wù)環(huán)境,通過設(shè)定一些請求參數(shù)來返回不同的響應(yīng)內(nèi)容,然后再被內(nèi)部系統(tǒng)調(diào)用,來保證調(diào)用端的正確性。構(gòu)建模擬環(huán)境時我們可以使用幾種不同的測試手段,如Dummy,F(xiàn)ake,Stubs,Spies,Mocks等??墒?,問題又來了,如果使用測試替身那如何能保證外部系統(tǒng)API變化時得到及時的響應(yīng),換句話說,當(dāng)內(nèi)部系統(tǒng)測試都通過的通過時,如何能保證真正的外部API沒有變化?
一個比較簡單的方式是部分測試使用測試替身,另外一部分測試定期調(diào)用真實的外部API,這樣既保證了測試的運行效率、調(diào)用端的準(zhǔn)確性,又能確保當(dāng)真實外部系統(tǒng)API改變時能得到反饋。
是不是到這里就皆大歡喜了呢?
如果劇情到這里就結(jié)束的話,未免太過俗套。這個方案最大的缺陷在于API的反應(yīng)速度,真實外部API的反饋周期過長,如果減少真實API測試間隔時間就又會回到文章最開始的兩難境地。
那么如何解決這個問題呢?先來讓我們剖析一下前面幾種解決方案的共通點。
在上面的場景中,我們都是已知外部API功能來編寫相應(yīng)的功能測試,并且使用直接調(diào)用外部API的方式來達到驗證測試的目的,這樣就不可避免的帶來兩個問題:
- 第一,服務(wù)消費方對服務(wù)提供方API的更改是通過對API的測試來感知的。
- 第二,直接依賴于真實API的測試效果受限于API的穩(wěn)定性和反映速度。
解決方式首先是依賴關(guān)系的解耦,去掉直接對外部API的依賴,而是內(nèi)部和外部系統(tǒng)都依賴于一個雙方共同認(rèn)可的約定—“契約”,并且約定內(nèi)容的變化會被及時感知;其次,將系統(tǒng)之間的集成測試,轉(zhuǎn)換為由契約生成的單元測試,例如通過契約描述的內(nèi)容,構(gòu)建測試替身。這樣,依賴契約的測試效率優(yōu)于集成測試,同時契約替代外部API成為信息變更的載體。
對于契約來講,行業(yè)內(nèi)比較成熟的解決方案是基于YAML標(biāo)記語言的Swagger Specification(OpenAPI Specification),或者是基于JSON格式的Pact Specification。
通常的做法是API的提供者使用“契約”的形式,將功能發(fā)布在公共平臺,給調(diào)用方進行說明和參考,這里我們可以暫時稱之為Provider-Driven-Contract。這種做法的潛在問題是,功能提供方的API返回內(nèi)容是否都滿足所有API調(diào)用者的需求不得而知。所以,針對這個問題,依賴關(guān)系再一次反轉(zhuǎn),契約測試就搖身一變成為了Consumer-Driven-Contract test(CDCT), 通過給API提供方提供契約的形式,來完成功能的實現(xiàn)。
難道CDCT成為了問題終結(jié)者嗎?請聽后面分解。
注: 契約測試其中一個的典型應(yīng)用場景是內(nèi)外部系統(tǒng)之間的測試,另一個典型的例子是前后端分離后的API測試,這里不做過多展開。
三、契約測試的維度
1. 測試覆蓋范圍對比(縱向)
- 單元測試:對軟件中的基本組成單位的測試,大多數(shù)是方法函數(shù)的測試,運行速度快。
- 契約測試:對服務(wù)之間的功能進行的測試,運行速度基本與單元測試相同。
- E2E 測試:對系統(tǒng)前后端或者不同系統(tǒng)之間的集成測試,大多通過模擬UI操作的方式實現(xiàn),運行速度三者之中最慢。
2. 測試效率對比(橫向)
環(huán)境依賴:
- 單元測試:程序集
- 契約測試:程序集、依賴契約文件、虛擬路由服務(wù)
- 端到端測試:程序集、真實路由服務(wù)、前端UI
- 運行速度: 單元測試 > 契約測試 > 端到端測試
Pact官方給出的幾個場景:
適用場景:
- 團隊能把控開發(fā)過程中的Consumer和Provider端
- 適合Consumer驅(qū)動開發(fā)的場景
- 對于每個獨立的Consumer端,Provider端都能管理好需求。
不適用的場景:
- 公共API或者是OAuth授權(quán)服務(wù)
- Provider端和Consumer端沒有良好的溝通渠道
- 針對性能的測試
- Provider端的功能性測試(Pact只測試內(nèi)容和請求格式)
- 對于不同輸入有相同的輸出,并未達到驗證的目的
- 當(dāng)前測試輸入需要依賴之前測試返回的結(jié)果
以上對比說明契約測試所要解決的問題是替代系統(tǒng)之間的集成測試,通過契約和單元測試的方式加速系統(tǒng)運行。同時也說明契約測試存在一些不適用的場景,要依據(jù)使用場景區(qū)別對待。契約測試沒有取代單元測試以及E2E測試。
四、契約測試與CD的整合
最開始,我們的pipeline是這樣的,單元測試是獨立的測試,當(dāng)通過單元測試后運行集成測試。此時集成測試成為了系統(tǒng)瓶頸,而且一旦集成測試失敗,就必須被迅速修復(fù),其他pipeline只能等待其修復(fù),否則任何新的變更都會測試失敗。
一個解決辦法是將集成測試分散在每個pipeline上,每次集成測試運行的版本是當(dāng)前的最新代碼和其他系統(tǒng)的上一次通過版本之間的測試。這樣解決了測試的獨立性以及不會阻礙其他pipeline測試的效果,然后將通過測試的不同系統(tǒng)的package按照版本保存。但是這樣一來,集成測試的缺點就更為明顯提現(xiàn)出來,第一是系統(tǒng)部署時間長,每次集成測試需要運行同樣的測試在不同pipeline上,增加了測試成本和反饋周期。
接下來,我們使用契約測試替代集成測試。這樣有幾點好處不僅解決了獨立測試的目的,同時解決了集成測試慢和部署時間長等問題。
為了保證契約測試的正確性,契約文件由Consumer端生成,然后Provider端來實現(xiàn)API,我們使用CDCT來改造我們的pipeline。
我們先假設(shè)B系統(tǒng)希望A系統(tǒng)提供新功能,如果按照圖中黃色步驟來提交的話,則會測試失敗,原因在于此時,契約文件是最新的B-A.consumer.1.1.pact與之對應(yīng)A-B.provider.1.0.jar不是最新的,所以測試失敗。
按照圖中步驟2運行,當(dāng)提交A的pipeline時,當(dāng)前版本的A已經(jīng)升級到1.1,而契約文件還是1.0版本,沒有break測試的情況下,最終將A-B.provider.1.1.jar提交到服務(wù)器上。
然后按照圖中步驟3運行,A-B.provider.1.1.jar和B-A.consumer.1.1.pact完美契合,最終又將B-A.consumer.1.1.pact提交到服務(wù)器。所以,改成CDCT之后,雖然產(chǎn)生了一定的提交順序依賴,但是帶來的更多的好處是確保契約文件的產(chǎn)生是調(diào)用端提出,并且保證當(dāng)前最新,確保系統(tǒng)的正確性。
喜歡思考的同學(xué)不難發(fā)現(xiàn),CDCT存在自身的缺陷,一個簡單的例子是當(dāng)B存在一個已有的契約約束A的一個功能,當(dāng)B需要A更新其API時,是先提交B的契約測試,還是更改A的功能到最新版本?其實二者都不可行。
解決辦法萬變不離其宗,就是大家熟悉的不能再熟悉的重構(gòu)心法,由王建總結(jié)的十六字箴言:
我們分五步來完成API的更新:
- Provider端提交一個新的API來保證新功能,同時舊的API功能不變,提交并通過測試。
- 將Consumer端API的調(diào)用指向Provider端的新API,并更新契約文件以約束新功能。
- 將Provider端舊API同步更新為新API,提交并通過測試。
- 將Consumer端指回舊有API,其他保持不變。
- 將Provider端臨時過渡的新API刪除。
至此,我們解決了API更新時如何保證契約測試的提交順序,如果是刪除API,則直接刪除Consumer端的契約測試即可。
五、需要思考的問題
1. 如果并行測試的話,誰先提交成功的版本,另外一個測試是否要重新運行?
設(shè)想,當(dāng)兩個并行pipeline A和B,同時運行時,A中跑的是A1.1和B1.0,B中跑的是B1.1和A1.0的測試,假如雙方均能通過各自的測試,但是新版本不兼容(A1.1和B1.1測試失敗),雙方都將各自的新版本保留,這樣就造成了存在相互不兼容的兩個版本。目前解決方案是,人為制造一個“瓶頸”,保證同時只有一個契約測試在運行,保存的只有一個版本。
2. 契約測試可維護性如何?
構(gòu)建契約測試類似于單元測試,并且在Pact的框架下十分方便維護。但是,測試框架本身還有一些問題,諸如,大小寫敏感,空值驗證,只有一份契約文件,契約測試分組等。
(以上是基于pact 1.0的實踐,pact2.0使用了正則表達式以及TypeMatching等機制解決了驗證“具體”值的問題,更多詳細(xì)內(nèi)容請關(guān)注pact官方文檔)
六、結(jié)語
契約測試不是銀彈,它不是替代E2E測試的終結(jié)者,更不是單元測試的升級換代,它更偏向于服務(wù)和服務(wù)之間的API測試,通過解耦服務(wù)依賴關(guān)系和單元測試來加快測試的運行效率。
【本文是專欄作者“ThoughtWorks”的原創(chuàng)稿件,微信公眾號:思特沃克,轉(zhuǎn)載請聯(lián)系原作者】
網(wǎng)頁名稱:聊一聊契約測試
轉(zhuǎn)載源于:http://fisionsoft.com.cn/article/coscijo.html


咨詢
建站咨詢
