這篇文章探討當(dāng)下最熱門的技術(shù)領(lǐng)域的API測試,即微服務(wù)模式下的API測試。微服務(wù)架構(gòu)下,API測試的最大挑戰(zhàn)來自于龐大的測試用例數(shù)量,以及微服務(wù)之間的相互耦合。這篇文章探討這兩個問題的本質(zhì),以及如何基于消費者契約的方法來應(yīng)對這兩個難題。
而為了掌握微服務(wù)模式下的API測試,需要先了解微服務(wù)架構(gòu)(Microservice Architecture)的特點、測試挑戰(zhàn);而要了解微服務(wù)架構(gòu),又需要先了解一些單體架構(gòu)(Monolithic Architecture)的知識。所以,這篇文章將逐層展開,目的就是希望可以真正理解,并快速掌握微服務(wù)模式下的API測試。
單體架構(gòu)(Monolithic Architecture)
單體架構(gòu)是早期的架構(gòu)模式,并且存在了很長時間。單體架構(gòu)是將所有的業(yè)務(wù)場景的表示層、業(yè)務(wù)邏輯層和數(shù)據(jù)訪問層放在同一個工程中,最終經(jīng)過編譯、打包,并部署在服務(wù)器上。
比如,經(jīng)典的J2EE工程,它就是將表示層的JSP、業(yè)務(wù)邏輯層的Service、Controller和數(shù)據(jù)訪問層的DAO(Data Access Objects),打包成war文件,然后部署在Tomcat、Jetty或者其他Servlet容器中運行。
顯然單體架構(gòu)具有發(fā)布簡單、方便調(diào)試、架構(gòu)復(fù)雜性低等優(yōu)點,所以長期以來一直被大量使用,并廣泛應(yīng)用于傳統(tǒng)企業(yè)級軟件。
但是,隨著互聯(lián)網(wǎng)產(chǎn)品的普及,應(yīng)用所承載的流量越來越龐大,單體架構(gòu)的問題也被逐漸暴露并不斷放大,主要的問題有以下幾點:
- 靈活性差:無論是多小的修改,哪怕只修改了一行代碼,也要打包發(fā)布整個應(yīng)用。更糟的是,由于所有模塊代碼都在一起,所以每次編譯打包都要花費很長時間。
- 可擴(kuò)展性差:在高并發(fā)場景下,無法以模塊為單位靈活擴(kuò)展容量,不利于應(yīng)用的橫向擴(kuò)展。
- 穩(wěn)定性差:當(dāng)單體應(yīng)用中任何一個模塊有問題時,都可能會造成應(yīng)用整體的不可用,缺乏容錯機(jī)制。
- 可維護(hù)性差:隨著業(yè)務(wù)復(fù)雜性的提升,代碼的復(fù)雜性也是直線上升,當(dāng)業(yè)務(wù)規(guī)模比較龐大時,整體項目的可維護(hù)性會大打折扣。
正是因為面對互聯(lián)網(wǎng)應(yīng)用時,單體架構(gòu)有這一系列無法逾越的鴻溝,所以催生了微服務(wù)架構(gòu)。
其實,微服務(wù)架構(gòu)也不是一蹴而就的,也經(jīng)歷了很長時間的演化發(fā)展,中間還經(jīng)歷了著名的SOA架構(gòu)。但是這個由單體架構(gòu)到SOA架構(gòu)再到微服務(wù)架構(gòu)的演進(jìn)過程,并不是本文的重點,所以我就不再詳細(xì)展開了,如果你感興趣的話,可以自行去查閱一些相關(guān)資料。
微服務(wù)架構(gòu)(Microservice Architecture)
微服務(wù)是一種架構(gòu)風(fēng)格。在微服務(wù)架構(gòu)下,一個大型復(fù)雜軟件系統(tǒng)不再由一個單體組成,而是由一系列相互獨立的微服務(wù)組成。其中,各個微服務(wù)運行在自己的進(jìn)程中,開發(fā)和部署都沒有依賴。
不同服務(wù)之間通過一些輕量級交互機(jī)制進(jìn)行通信,例如 RPC、HTTP 等,服務(wù)可獨立擴(kuò)展伸縮,每個服務(wù)定義了明確的邊界,只需要關(guān)注并很好地完成一件任務(wù)就可以了,不同的服務(wù)可以根據(jù)業(yè)務(wù)需求實現(xiàn)的便利性而采用不同的編程語言來實現(xiàn),由獨立的團(tuán)隊來維護(hù)。
圖1就很形象地展示了單體架構(gòu)和微服務(wù)架構(gòu)之間的差異。
圖1 單體架構(gòu) VS 微服務(wù)架構(gòu)
微服務(wù)架構(gòu)具有以下特點:
- 每個服務(wù)運行在其獨立的進(jìn)程中,開發(fā)采用的技術(shù)棧也是獨立的;
- 服務(wù)間采用輕量級通信機(jī)制進(jìn)行溝通,通常是基于HTTP協(xié)議的RESTful API;
- 每個服務(wù)都圍繞著具體的業(yè)務(wù)進(jìn)行構(gòu)建,并且能夠被獨立開發(fā)、獨立部署、獨立發(fā)布;
- 對運維提出了非常高的要求,促進(jìn)了CI/CD的發(fā)展與落地。
微服務(wù)架構(gòu)下的測試挑戰(zhàn)
由于微服務(wù)架構(gòu)下,一個應(yīng)用是由很多相互獨立的微服務(wù)組成,每個微服務(wù)都會對外暴露接口,同時這些微服務(wù)之間存在級聯(lián)調(diào)用關(guān)系,也就是說一個微服務(wù)通常還會去調(diào)用其他微服務(wù),鑒于以上特點,微服務(wù)架構(gòu)下的測試挑戰(zhàn)主要來自于以下兩個方面:
- 過于龐大的測試用例數(shù)量;
- 微服務(wù)之間的耦合關(guān)系。
接下來,我會針對這兩項挑戰(zhàn)分別展開,包括它們從何而來,以及如何應(yīng)對這些挑戰(zhàn),最終完成測試。
第一,過于龐大的測試用例數(shù)量
在傳統(tǒng)的API測試中,我們的測試策略通常是:
- 根據(jù)被測API輸入?yún)?shù)的各種組合調(diào)用API,并驗證相關(guān)結(jié)果的正確性;
- 衡量上述測試過程的代碼覆蓋率;
- 根據(jù)代碼覆蓋率進(jìn)一步找出遺漏的測試用例;
- 以代碼覆蓋率達(dá)標(biāo)作為API測試成功完成的標(biāo)志。
這也是單體架構(gòu)時代主流的API測試策略。為了讓你更好地理解這種測試策略,我來舉一個實際的例子。
假設(shè)我們采用單體架構(gòu)開發(fā)了一個系統(tǒng),這個系統(tǒng)對外提供了3個Restful API接口,那么我們的測試策略應(yīng)該是:
- 針對這3個API接口,分別基于邊界值和等價類方法設(shè)計測試用例并執(zhí)行;
- 在測試執(zhí)行過程中,啟用代碼覆蓋率統(tǒng)計;
- 假設(shè)測試完成后代碼行覆蓋率是80%,那么我們就需要找到那些還沒有被執(zhí)行到的20%的代碼行。比如圖2中代碼的第242行就是沒有被執(zhí)行到,分析代碼邏輯后發(fā)現(xiàn),我們需要構(gòu)造“expected!=actual”才能覆蓋這個未能執(zhí)行的代碼行;
- 最終我們要保證代碼覆蓋率達(dá)到既定的要求,比如行覆蓋率達(dá)到100%,完成API測試。
圖2 基于代碼覆蓋率指導(dǎo)測試用例設(shè)計的示例
而當(dāng)我們采用微服務(wù)架構(gòu)時,原本的單體應(yīng)用會被拆分成多個獨立模塊,也就是很多個獨立的service,原本單體應(yīng)用的全局功能將會由這些拆分得到的API共同協(xié)作完成。
比如,對于上面這個例子,沒有微服務(wù)化之前,一共有3個API接口,假定現(xiàn)在采用微服務(wù)架構(gòu),該系統(tǒng)被拆分成了10個獨立的service,如果每個service平均對外暴露3個API接口,那么總共需要測試的API接口數(shù)量就多達(dá)30個。
如果我還按照傳統(tǒng)的API測試策略來測試這些API,那么測試用例的數(shù)量就會非常多,過多的測試用例往往就需要耗費大量的測試執(zhí)行時間和資源。
但是,在互聯(lián)網(wǎng)模式下,產(chǎn)品發(fā)布的周期往往是以“天”甚至是以“小時”為單位的,留給測試的執(zhí)行時間非常有限,所以微服務(wù)化后API測試用例數(shù)量的顯著增長就對測試發(fā)起了巨大的挑戰(zhàn)。
這時,我們迫切需要找到一種既能保證API質(zhì)量,又能減少測試用例數(shù)量的測試策略,這也就是我接下來要分享的基于消費者契約的API測試。
第二,微服務(wù)之間的耦合關(guān)系
微服務(wù)化后,服務(wù)與服務(wù)間的依賴也可能會給測試帶來不小的挑戰(zhàn)。
如圖3所示,假定我們的被測對象是Service T,但是Service T的內(nèi)部又調(diào)用了Service X和Service Y。此時,如果Service X和Service Y由于各種原因處于不可用的狀態(tài),那么此時就無法對Service T進(jìn)行完整的測試。
圖3 API之間的耦合示例
我們迫切需要一種方法可以將Service T的測試與Service X和Service Y解耦。
解耦的方式通常就是實現(xiàn)Mock Service來代替被依賴的真實Service。實現(xiàn)這個Mock Service的關(guān)鍵點就是要能夠模擬真實Service的Request和Response。當(dāng)我介紹完基于消費者契約的API測試后,你會發(fā)現(xiàn)這個問題也就迎刃而解了。
基于消費者契約的API測試
那到底什么是基于消費者契約的API測試呢?直接從概念的角度解釋,會有些難以理解。所以我打算換個方法來幫助你從本質(zhì)上真正理解什么是基于消費者契約的API測試。接下來,就跟著我的思路走吧。
首先,我們來看圖4,假設(shè)圖4中的Service A、Service B和Service T是微服務(wù)拆分后的三個Service,其中Service T是被測試對象,進(jìn)一步假定Service T的消費者(也就是使用者)一共有兩個,分別是Service A和Service B。
圖4 Service A、Service B和Service T的關(guān)系
按照傳統(tǒng)的API測試策略,當(dāng)我們需要測試Service T時,需要找到所有可能的參數(shù)組合依次對Service T進(jìn)行調(diào)用,同時結(jié)合Service T的代碼覆蓋率進(jìn)一步補充遺漏的測試用例。
這種思路本身沒有任何問題,但是測試用例的數(shù)量會非常多。那我們就需要思考,如何既能保證Service T的質(zhì)量,又不需要覆蓋全部可能的測試用例。
靜下心來想一下,你會發(fā)現(xiàn)Service T的使用者是確定的,只有Service A和Service B,如果可以把Service A和Service B對Service T所有可能的調(diào)用方式都測試到,那么就一定可以保證Service T的質(zhì)量。即使存在某些Service T的其他調(diào)用方式有出錯的可能性,那也不會影響整個系統(tǒng)的功能,因為這個系統(tǒng)中并沒有其他Service會以這種可能出錯的方式來調(diào)用Service T。
現(xiàn)在,問題就轉(zhuǎn)化成了如何找到Service A和Service B對Service T所有可能的調(diào)用方式。如果能夠找出這樣的調(diào)用集合,并以此作為Service T的測試用例,那么只要這些測試用例100%通過,Service T的質(zhì)量也就不在話下了。
從本質(zhì)上來講,這樣的測試用例集合其實就是,Service T可以對外提供的服務(wù)的契約,所以我們把這個測試用例的集合稱為“基于消費者契約的API測試”。
那么接下來,我們要解決的問題就是:如何才能找到Service A和Service B對Service T的所有可能調(diào)用了。其實這也很簡單,在邏輯結(jié)構(gòu)上,我們只要在Service T前放置一個代理,所有進(jìn)出Service T的Request和Response都會經(jīng)過這個代理,并被記錄成JSON文件,也就構(gòu)成了Service T的契約。
如圖5所示,就是這個過程的原理了。
圖5 收集消費者契約的邏輯原理
在實際項目中,我們不可能在每個Service前去放置這樣一個代理。但是,微服務(wù)架構(gòu)中往往會存在一個叫作API Gateway的組件,用于記錄所有API之間相互調(diào)用關(guān)系的日志,我們可以通過解析API Gateway的日志分析得到每個Service的契約。
至此,我們已經(jīng)清楚地知道了如何獲取Service的契約,并由此來構(gòu)成Service的契約測試用例。接下來,就是如何解決微服務(wù)之間耦合關(guān)系帶來的問題了。
微服務(wù)測試的依賴解耦和Mock Service
在前面的內(nèi)容中,我說過一句話:實現(xiàn)Mock Service的關(guān)鍵,就是要能夠模擬被替代Service的Request和Response。
此時我們已經(jīng)拿到了契約,契約的本質(zhì)就是Request和Response的組合,具體的表現(xiàn)形式往往是JSON文件,此時我們就可以用該契約的JSON文件作為Mock Service的依據(jù),也就是在收到什么Request的時候應(yīng)該回復(fù)什么Response。
下面的圖6就解釋了這一關(guān)系,當(dāng)用Service X的契約啟動Mock Service X后,原本真實的Service X將被Mock Service X替代,也就解耦了服務(wù)之間的依賴,圖6中的Service Y也是一樣的道理。
圖6 基于Mock Service解決API之間的調(diào)用依賴
代碼實例
自此,已經(jīng)講完了基于消費者契約的API測試的原理。
由于這部分內(nèi)容的理論知識比較多,為了更好地理解這些概念,找了一個基于Spring Cloud Contract的實際代碼的示例演示契約文件格式、消費者契約測試以及微服務(wù)之間解耦,希望可以幫到你。
具體的實例代碼,你可以從GitHub - SpectoLabs/spring-cloud-contract-blog下載,詳細(xì)的代碼解讀可以參考Consumer-Driven Contract Testing with Spring Cloud Contract | API simulations for development and testing。
這個實例代碼,基于Spring Boot實現(xiàn)了兩個微服務(wù):訂閱服務(wù)(subscription-service)和賬戶服務(wù)(account-service),其中訂閱服務(wù)會調(diào)用賬戶服務(wù)。這個實例基于Spring Cloud Contract,所以契約是通過Groovy語言描述的,也就是說實例中會通過Groovy語言描述的賬戶服務(wù)契約來模擬真實的賬戶服務(wù)。
這個實例的邏輯關(guān)系如圖7所示。
圖7 基于Spring Cloud Contract的契約測試實例
總結(jié)
單體架構(gòu),具有靈活性差、可擴(kuò)展性差、可維護(hù)性差等局限性,所以有了微服務(wù)架構(gòu)。
微服務(wù)架構(gòu)的本身的特點,比如微服務(wù)數(shù)量多,各個微服務(wù)之間的相互調(diào)用,決定了不能繼續(xù)采用傳統(tǒng)API測試的策略。
為了既能保證API質(zhì)量,又能減少測試用例數(shù)量,于是有了基于消費者契約的API測試?;谙M者契約的API測試的核心思想是:只測試那些真正被實際使用到的API調(diào)用,如果沒有被使用到的,就不去測試。文章來源:http://www.zghlxwxcb.cn/news/detail-836979.html
基于消費者契約的測試方法,由于收集到了完整的契約,所以基于契約的Mock Service完美地解決了API之間相互依賴耦合的問題。文章來源地址http://www.zghlxwxcb.cn/news/detail-836979.html
到了這里,關(guān)于【軟件測試】學(xué)習(xí)筆記-微服務(wù)模式下API測試的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!