驗(yàn)收測(cè)試讓交付團(tuán)隊(duì)超越了基本的持續(xù)集成,即驗(yàn)證應(yīng)用程序是否為用戶提供了有價(jià)值的功能。不過(guò)對(duì)于剛開(kāi)始嘗試部署流水線的團(tuán)隊(duì)來(lái)說(shuō),想要自動(dòng)化驗(yàn)收測(cè)試,需要跨過(guò)三大門(mén)檻。
一是實(shí)現(xiàn)和維護(hù)驗(yàn)收測(cè)試的技術(shù)門(mén)檻。理想情況下,驗(yàn)收測(cè)試最好可以模擬用戶與應(yīng)用程序的真實(shí)交互,因此如果有圖形界面的話,驗(yàn)收測(cè)試?yán)響?yīng)通過(guò)這個(gè)界面和系統(tǒng)打交道。然而,直接通過(guò)GUI進(jìn)行測(cè)試會(huì)遇到幾個(gè)問(wèn)題:界面變化速度很快、場(chǎng)景的準(zhǔn)備相對(duì)復(fù)雜、拿到測(cè)試結(jié)果較難等。比如一個(gè)典型的WEB應(yīng)用程序,如果通過(guò)GUI測(cè)試,那么一般需要解析HTML標(biāo)簽來(lái)填寫(xiě)參數(shù),提交表單,最后再次通過(guò)解析來(lái)獲取系統(tǒng)的返回值。如果測(cè)試代碼中充斥著操作HTML的細(xì)節(jié),測(cè)試的可讀性就會(huì)大大下降,驗(yàn)收測(cè)試本身也更脆弱,在需求變更時(shí)反而會(huì)拖慢進(jìn)度。
二是交付團(tuán)隊(duì)工作方式的變化。在傳統(tǒng)團(tuán)隊(duì)中,需求分析、開(kāi)發(fā)和測(cè)試是獨(dú)立而又順序的過(guò)程。就算能形成詳細(xì)的需求文檔,三方對(duì)同一段文字可能都有自己的理解。結(jié)果經(jīng)常出現(xiàn)偏差,需求分析人員抱怨開(kāi)發(fā)人員沒(méi)有正確理解需求文檔,開(kāi)發(fā)人員抱怨需求文檔不清晰、抱怨測(cè)試人員故意挑刺。敏捷實(shí)踐和驗(yàn)收測(cè)試的出現(xiàn)緩解了這一問(wèn)題,通過(guò)預(yù)先定義驗(yàn)收規(guī)格,減少文字上的誤解,明確了開(kāi)發(fā)工作的完成標(biāo)準(zhǔn)。不過(guò)這種思維方式的轉(zhuǎn)變很難一蹴而就,需要交付團(tuán)隊(duì)及其利益關(guān)系人共同持續(xù)努力才能成功。
三是對(duì)組織的環(huán)境、配置管理及部署流程的挑戰(zhàn)。當(dāng)引入自動(dòng)化驗(yàn)收測(cè)試后,對(duì)整個(gè)部署流水線的自動(dòng)化程度會(huì)有更高要求。比如部署流水線應(yīng)該能夠自動(dòng)將應(yīng)用程序部署到待測(cè)試的環(huán)境中。如果應(yīng)用程序依賴數(shù)據(jù)庫(kù),那么還應(yīng)該能夠部署數(shù)據(jù)庫(kù)schema。另外一些運(yùn)行時(shí)配置也需要通過(guò)腳本完成設(shè)置。這當(dāng)中除了腳本準(zhǔn)備之外,組織的環(huán)境管理也是要能跟上的。一般情況下,稍微大一些的組織都是有專門(mén)的運(yùn)維團(tuán)隊(duì)(而非交付團(tuán)隊(duì))來(lái)管理硬件設(shè)備和其配置的。因此,這個(gè)問(wèn)題一般也涉及多個(gè)團(tuán)隊(duì)來(lái)協(xié)作解決。
面對(duì)這三座大山和進(jìn)度壓力,新手團(tuán)隊(duì)可能會(huì)感慨“信息量略大”而止步不前。這時(shí)不妨考慮各個(gè)擊破,三個(gè)問(wèn)題中的工作方式轉(zhuǎn)變涉及的利益干系人最多,難度也最大;環(huán)境管理問(wèn)題雖然涉及不同團(tuán)隊(duì),但一般還是技術(shù)部門(mén)內(nèi)的問(wèn)題,關(guān)起門(mén)來(lái)好商量;驗(yàn)收測(cè)試的實(shí)現(xiàn)/維護(hù)主要是技術(shù)問(wèn)題,相對(duì)最簡(jiǎn)單。如果時(shí)間和資源確實(shí)有限,不妨考慮犧牲一部分驗(yàn)收測(cè)試的有效性,采用簡(jiǎn)單的非端到端驗(yàn)收測(cè)試,在自動(dòng)化部署流程方面也可以做一些折中,集中力量轉(zhuǎn)變工作方式。當(dāng)整個(gè)工作已經(jīng)進(jìn)入節(jié)奏,再去改進(jìn)某個(gè)具體環(huán)節(jié)時(shí)就順利很多了。團(tuán)隊(duì)只要愿意邁出一小步,也能獲得很大的價(jià)值。
過(guò)渡方案:相對(duì)簡(jiǎn)單的非端到端驗(yàn)收測(cè)試
如果團(tuán)隊(duì)的技術(shù)積累還不足,又沒(méi)有足夠的資源,不妨考慮簡(jiǎn)單一些的驗(yàn)收測(cè)試策略作為過(guò)渡方案。非端到端的驗(yàn)收測(cè)試是指直接調(diào)用應(yīng)用程序內(nèi)部的邏輯結(jié)構(gòu)來(lái)驅(qū)動(dòng)測(cè)試。由于測(cè)試代碼和產(chǎn)品代碼都使用同一種語(yǔ)言編寫(xiě),可以省去比較繁瑣的數(shù)據(jù)格式解析。而在準(zhǔn)備測(cè)試數(shù)據(jù)和場(chǎng)景時(shí),直接調(diào)用內(nèi)部邏輯塊一般也更方便。以典型的使用SpringFramework的Java WEB應(yīng)用程序?yàn)槔瑘F(tuán)隊(duì)可以采用和集成測(cè)試類似的基礎(chǔ)架構(gòu)來(lái)編寫(xiě)非端到端的驗(yàn)收測(cè)試。
這里所說(shuō)的集成測(cè)試的目的是驗(yàn)證應(yīng)用程序與外部服務(wù)的連接能否正常工作。這與應(yīng)用程序?qū)崿F(xiàn)的具體功能關(guān)系不大,因此一般只加載必需的ApplicationContext。
圖表 1 集成測(cè)試
非端到端的驗(yàn)收測(cè)試可以采用和集成測(cè)試一樣的測(cè)試基礎(chǔ)架構(gòu),這樣你就可以使用熟悉的測(cè)試庫(kù)了,不同的是需要加載整個(gè)ApplicationContext以盡可能模擬應(yīng)用程序被部署后的情況。
圖表 2 加載整個(gè)上下文
由于可以訪問(wèn)整個(gè)ApplicationContext中的任一對(duì)象,我們可以通過(guò)訪問(wèn)應(yīng)用程序的內(nèi)部組件來(lái)執(zhí)行測(cè)試,比如應(yīng)用層的某個(gè)Service。但需要注意的是,選取的組件離UI層越遠(yuǎn),其模擬真實(shí)用戶交互的有效性就越差,而且受內(nèi)部實(shí)現(xiàn)變更的影響越大。如果應(yīng)用程序使用spring-webmvc的3.2以上版本,推薦使用它的mvc測(cè)試庫(kù)。spring-test-mvc提供了類似http請(qǐng)求的DSL,此時(shí)雖然測(cè)試還是基于ApplicationContext,但并不直接訪問(wèn)內(nèi)部組件了。這個(gè)方案對(duì)于新手團(tuán)隊(duì)比較友善,但請(qǐng)注意,這僅僅是個(gè)過(guò)渡方案,因?yàn)椋?/p>
非端到端測(cè)試無(wú)法提供全面的回歸測(cè)試,尤其是UI操作。在好幾個(gè)項(xiàng)目中,我們發(fā)現(xiàn)僅采用非端到端測(cè)試覆蓋的功能,團(tuán)隊(duì)不得不保留手工回歸測(cè)試。如果UI上包含了大量復(fù)雜的控制邏輯甚至有業(yè)務(wù)邏輯泄漏到UI組件中,這會(huì)稀釋驗(yàn)收測(cè)試帶來(lái)的收益。
由于測(cè)試加載的ApplicationContext和Web容器加載的ApplicationContext存在差異,非端到端測(cè)試可能會(huì)漏掉一些問(wèn)題。比如在非端到端測(cè)試中一次性加載了booking-servlet.xml和root.xml,使他們成為了一個(gè)整體的上下文,而實(shí)際上在Web容器中并不完全是這樣,root.xml中的bean并不能訪問(wèn)和控制booking-servlet.xml中的bean。一個(gè)常見(jiàn)問(wèn)題就是如果在booking-servlet.xml中需要使用占位符,而恰巧我們已經(jīng)在root.xml中有一個(gè)現(xiàn)成的,看起來(lái)水到渠成,而且在測(cè)試中也沒(méi)有問(wèn)題,但實(shí)際部署到web容器時(shí),就會(huì)加載失敗。
非端到端的驗(yàn)收測(cè)試不能作為任務(wù)完成的最終標(biāo)準(zhǔn)。因?yàn)檫€有UI部分還沒(méi)有完成。當(dāng)這類驗(yàn)收測(cè)試通過(guò)時(shí),我把這個(gè)任務(wù)稱作“可以進(jìn)入U(xiǎn)I調(diào)試的”。
因此,如果團(tuán)隊(duì)有足夠的技能和資源時(shí)還是應(yīng)該直接使用端到端的驗(yàn)收測(cè)試,尤其當(dāng)應(yīng)用程序提供API(比如WebService)或是采用更易于解析的數(shù)據(jù)格式與客戶端交互時(shí)。比如如果應(yīng)用程序提供了基于JSON的API,完全可以使用http-client來(lái)驅(qū)動(dòng)測(cè)試。
實(shí)現(xiàn)非端到端的驗(yàn)收測(cè)試
來(lái)看看第一個(gè)驗(yàn)收測(cè)試,這個(gè)案例來(lái)自于著名的dddsample,為了讓驗(yàn)收測(cè)試能夠看上去高端大氣上檔次,我們將使用Cucumber來(lái)組織驗(yàn)收測(cè)試。驗(yàn)收?qǐng)鼍懊枋龅氖菢I(yè)務(wù)員如何登記航運(yùn)貨件并解釋了登記完成后貨件的各項(xiàng)狀態(tài)。
圖表 3 第一個(gè)用戶故事及其驗(yàn)收?qǐng)鼍?/p>
Cucumber提供了一系列的Annotation來(lái)幫助我們驗(yàn)收?qǐng)鼍拔谋九c測(cè)試代碼粘連在一起。
圖表 4實(shí)現(xiàn)驗(yàn)收測(cè)試-1
圖表 5 實(shí)現(xiàn)驗(yàn)收測(cè)試-2
接下來(lái),當(dāng)運(yùn)行測(cè)試時(shí),你就可以得到一份漂亮的html報(bào)告
圖表 6 測(cè)試運(yùn)行入口
維護(hù)非端到端的驗(yàn)收測(cè)試
當(dāng)團(tuán)隊(duì)開(kāi)始編寫(xiě)驗(yàn)收測(cè)試之后,一般沒(méi)過(guò)多久就會(huì)發(fā)現(xiàn)驗(yàn)收測(cè)試的開(kāi)發(fā)進(jìn)度越來(lái)越慢,而且有時(shí)遇到測(cè)試失敗,但其實(shí)應(yīng)用程序并沒(méi)有缺陷的情況。驗(yàn)收測(cè)試對(duì)代碼質(zhì)量的要求也很高,相比單元測(cè)試,為了要達(dá)到測(cè)試所需的起始狀態(tài),驗(yàn)收測(cè)試的準(zhǔn)備工作要更復(fù)雜。而且由于需要解析應(yīng)用程序返回的數(shù)據(jù),驗(yàn)收測(cè)試的斷言也會(huì)更加瑣碎。因此,團(tuán)隊(duì)最好盡早開(kāi)始重構(gòu)驗(yàn)收測(cè)試,下面的建議或許有用處:
建立最小測(cè)試數(shù)據(jù)集并且盡可能隔離測(cè)試的數(shù)據(jù)。有時(shí)團(tuán)隊(duì)會(huì)發(fā)現(xiàn)兩組測(cè)試由于依賴同一批數(shù)據(jù)而產(chǎn)生沖突,單獨(dú)執(zhí)行任一組測(cè)試都能通過(guò),但一起執(zhí)行就會(huì)失敗。比如在示例代碼中,對(duì)于不同的貨件處理事件登記場(chǎng)景,驗(yàn)收測(cè)試都會(huì)注冊(cè)一個(gè)新的貨件。
隱藏?cái)嘌约?xì)節(jié)。這樣可以減少重復(fù)代碼,并提升測(cè)試的可讀性。把瑣碎的解析邏輯隱藏在領(lǐng)域語(yǔ)言編寫(xiě)的方法中。
例如:如果多個(gè)測(cè)試用例都會(huì)對(duì)貨件的運(yùn)輸狀態(tài)進(jìn)行斷言,可以把解析細(xì)節(jié)提取出來(lái),這樣可以去除重復(fù)代碼,并且減少語(yǔ)法噪聲。
圖表 7 抽取斷言
盡可能使用已實(shí)現(xiàn)的功能來(lái)實(shí)現(xiàn)測(cè)試場(chǎng)景的準(zhǔn)備。有一些步驟可能是多個(gè)測(cè)試用例都需要來(lái)準(zhǔn)備數(shù)據(jù)的,可以把此類步驟抽取出來(lái)。這樣也可以減少重復(fù)的代碼,當(dāng)應(yīng)用程序隨著需求變化時(shí),驗(yàn)收測(cè)試會(huì)有更強(qiáng)的適應(yīng)性,而且抽取出來(lái)的方法由于隱藏了技術(shù)細(xì)節(jié),使用起來(lái)更簡(jiǎn)練。直接使用數(shù)據(jù)腳本的方案看起來(lái)很誘人,但一旦內(nèi)部結(jié)構(gòu)改變,數(shù)據(jù)腳本也得跟著改。
圖表 8 抽取公共步驟
驗(yàn)收測(cè)試對(duì)實(shí)踐部署流水線的團(tuán)隊(duì)有著重要意義,也是很大的挑戰(zhàn)。希望大家都能找到合適自己的方法。最后介紹幾個(gè)有用的測(cè)試庫(kù)。
Moco,當(dāng)有外部系統(tǒng)集成需求時(shí),集成測(cè)試和驗(yàn)收測(cè)試的一大利器。在示例代碼中你可以找到一處例子。
GreenMail,如果應(yīng)用程序需要發(fā)送郵件的話,它可以提供一臂之力。不過(guò)在部署流水線上的端到端驗(yàn)收測(cè)試中,由于一般應(yīng)用程序和測(cè)試并不運(yùn)行在同一臺(tái)機(jī)器上,很難對(duì)郵件進(jìn)行直接的斷言。這時(shí)一種方案是修改應(yīng)用程序的架構(gòu),把發(fā)送郵件的實(shí)現(xiàn)分離到一個(gè)專用的應(yīng)用中去并使用消息隊(duì)列集成。那么在驗(yàn)收測(cè)試中,我們就可以通過(guò)監(jiān)聽(tīng)對(duì)應(yīng)的消息隊(duì)列來(lái)斷言了。
Awaitility,在需要對(duì)異步處理進(jìn)行斷言時(shí)有所幫助。