天天看點

《Cucumber:行為驅動開發指南》——6.3 照管好你的測試

本節書摘來自異步社群《cucumber:行為驅動開發指南》一書中的第6章,第6.3節,作者:【英】matt wynne , 【挪】aslak hellesy著,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

自動化特性的好處在于你可以把它們作為活文檔來長期信賴,因為你會将每一個場景都用于檢查産品代碼,以確定它們仍然有效。對于同代碼打交道的程式員來說,這還有另一件好處:在他們開發系統的時候,那些測試可以充當安全網,對任何破壞已有行為的錯誤都給出警告。

是以,你的特性可以充當一種回報機制,對整個團隊來說提供關于系統行為的回報,對程式員來說還能提供是否破壞已有行為的回報。想讓這些回報循環帶來好處,測試需要執行迅速,還需要可靠。我們首先來看看影響測試可靠性的問題。

6.3.1 滲露的場景

cucumber的場景從根本上講就是狀态轉換測試:你将系統置于給定的(given)狀态a,執行動作x(when),然後(then)驗證它遷移到了期望的狀态b。是以,每個場景在開始運作之前都需要系統處于某種确定的狀态,而每個場景結束時也要把系統置于一種髒的新狀态。

如果在兩個測試之間沒有重置系統的狀态,我們就說狀态在測試之間發生了滲露(leak)。這是導緻測試脆弱的主要原因之一。

如果一個場景需依賴之前另一個場景留下的狀态才能通過,就說明你在兩個場景之間制造了依賴。如果有一連串的場景像火車車廂一樣彼此依賴,那麼離火車事故就為時不遠了。

如果第一個場景,也就是正好按照下一個場景的需要将系統狀态準備好的那個場景,發生了變化,那麼突然之間後面那個就要失敗了。即使沒有改動前面一個,可如果你隻想單獨運作第二個場景,那又會怎樣呢?沒有了前一個場景滲露下來的狀态,它還是會失敗。

這種情況的反面,即獨立的場景,可以確定場景将系統置于幹淨的狀态,然後再在上面添加自己的資料。這使場景能夠自給自足,而不是跟其他測試留下的資料或共享的固件資料耦合到一起。投入些時間精力來構造一個良好可靠的測試資料構造器的庫,可以更容易達到獨立場景的目标。

獨立的場景對于成功自動化測試的重要性怎麼強調都不為過。除了獨立設定自身資料的場景所帶來的附加可靠性之外,它們讀起來、調試起來都更加清楚。當你僅靠閱讀場景就能準确地看清它所使用的資料,而不需要在固件資料腳本中甚至更麻煩地直接在資料庫中四處查閱時,了解或診斷一次失敗就容易多了。

測試資料構造器

如果你使用ruby,那肯定熟悉factorygirla這個gem。factorygirl是測試資料構造器(test data builder)b這一模式的出色實作。如果你還不太了解,以下内容簡單概括了它的好處。

假如你正在測試一個工資單系統,作為某個場景的一部分,你需要建立一條paycheck記錄。按照你的領域模型結構,paycheck需要一個employee,而employee需要一個address。每種結構還有其他一些必要的字段。你不需要在步驟定義代碼中分别建立所有這些對象,也不必維護一大堆固件資料,隻需要這樣做:

基于你的資料模型,隻要用模型的結構配置好factorygirl(配置的細節參閱factorygirl文檔),然後你隻需要向它要一個paycheck,factorygirl便會建立好paycheck記錄以及它所依賴的所有對象,并用适當的預設值設定好那些必要的字段。如果你喜歡讓某個字段擁有特定的值,可以讓factorygirl把預設值覆寫掉:

當資料的建立可以如此友善時,你就不再需要走到哪裡都拖着一大包固件資料。當然,建立這樣的構造器需要一小筆前期投資,但很快就會取得回報,那就是可靠的、可讀的場景和步驟定義代碼。如果你的團隊不使用ruby也不要緊,隻需要很少的額外工作,你仍然可以讓activerecord和factorygirl指向其資料庫。如果不行,你還可以針對自己使用的語言找找其他類似工具。

6.3.2 競争條件和打瞌睡的步驟

給一個足夠複雜的系統編寫端到端內建測試時,你終歸會遇到競争條件和沉睡的步驟的問題。當系統的兩個或多個部分并發執行,但執行是否成功需仰仗其中某一部分首先結束,這時就會發生競争條件。就cucumber測試來說,你的when步驟可能導緻系統啟動一些背景運作的工作,比如産生一份pdf或者更新一組搜尋索引。如果背景任務碰巧在cucumber

matt說:fixture這一術語有不同的含義

在自動化測試領域,詞語fixture至少有三種意思,這有時會引起混淆。在這一章我們使用術語固件資料(fixture data)表示用來給場景或測試用例設定上下文的資料。這是該術語在各種xunit測試工具a以及ruby on rails架構中使用的最常見的意思。

有一種古老的傳統(源于開創測試固件這一術語的硬體世界)是将測試系統與被測系統之間的連接配接稱為fixture。這是“粘合代碼”的角色,本書中我們稱之為自動化代碼(automation code)。fit測試架構b用的就是該術語的這種含義。

一些單元測試工具(如nunit)把水攪得更混了,它們把測試用例類本身稱為fixture。關于通用語言,就說到這裡吧!

執行then步驟之前結束,場景就會通過。而如果cucumber赢得了競争,then步驟在背景任務執行之前便執行了,那場景則會失敗。

如果競争雙方勢均力敵,你會遇上一個閃爍的場景,場景間歇地成功或失敗。而如果一方勝算較多,競争條件就會長期存在而不為人所知,直到某一天系統中某處新的變化平均了雙方的籌碼,場景便開始随機地失敗了。

針對這種問題,一種粗糙的解決方法是在場景中引入固定時長的停頓或睡眠,進而為系統騰出時間來完成背景任務的處理。診斷競争條件時這絕對是一種有用的短期技巧,然而,你還是應當抵住誘惑,一旦弄清了問題的原因就不要在測試中留下睡眠。引入打瞌睡的步驟不會解決場景閃爍的問題,隻會讓它發生的機率更低。同時,引入睡眠也會為測試的整體運作時間再增加額外的幾秒,用一個問題換一個問題罷了。

如果一定要做選擇,我們甯可要緩慢但可靠的測試也不要更快但不可靠的測試,不過我們沒必要做這樣的折中。當測試人員和程式員結對将場景自動化的時候,他們可以基于對系統工作原理的了解精心地編寫測試。也就是說,他們可以利用系統中隐含的線索讓測試知道何時能夠安全前進,因而測試可以盡快地前進,而不必使用粗陋的定長睡眠。關于處理異步代碼的示例及更多細節,可以參閱第9章。

6.3.3 共享的環境

在那些從手工驗收測試體系轉向使用自動化驗收測試的團隊中,共享的環境是我們經常發現的一個問題。按照傳統方法,團隊中的手工測試人員會使用一種稱為系統測試環境的特殊環境,其中部署系統最近的建構版本。測試人員将在這一環境中運作自己的手工測試,并将bug報告給開發團隊。如果有多名測試人員需要在同樣的環境上運作測試,他們就需要彼此溝通,確定不會影響對方。

團隊開始将測試自動化的時候,在一套新的環境中安裝系統哪怕稍微有點麻煩,大家都很可能本着最小阻力的原則,把各自的測試腳本全弄到這套已有的系統測試環境中來。現在測試環境不僅在團隊成員之間共享,也在測試腳本之間共享了。假設某一名開發人員收到了一個bug報告,想親自重制一下,可他并未意識到自動化測試此時也在運作。作為bug重制步驟的一部分,開發人員無心地删除了自動化測試所依賴的一條資料庫記錄,自動化測試自然失敗了。這種情形是導緻閃爍的場景的一種典型情況。

對單一環境的共享使用還會對資料庫這類炙手可熱的資源造成沉重且不穩定的負荷,進而促成不可靠的測試。當共享的資料庫超負荷運轉時,正常情況下的可靠測試也會因逾時而失敗。

要解決這一問題,在新的環境中啟動系統必須做到易如反掌。

6.3.4 被隔離的測試人員

測試人員在軟體團隊中常被視為二等公民。我們将在第 8 章中解釋,開發一組健康的cucumber特性套件不隻需要測試技巧,也需要程式設計技巧。如果測試人員被晾在一邊獨自建構cucumber測試,他們可能不具備讓步驟定義和支援代碼組織良好的軟體工程技巧。不知不覺中,測試會變得一團雜亂,且脆弱得沒人敢去改動。

為克服這一問題,編寫步驟定義和支援代碼時要鼓勵測試人員和程式員協同工作。程式員可以向測試人員展示如何組織代碼,使之結構清晰,如何提取可重用元件和庫,以供其他團隊使用。有些庫,比如capybara(參閱第15章),就是在程式員從團隊的步驟定義中提取可重用代碼時這樣産生出來的。通過與測試人員結對,程式員對如何讓代碼可測試也會産生更深的了解。

一個團隊如果把cucumber用到好處,測試人員應該能夠将運作基礎檢查的工作托付給cucumber。這樣他們自己就可以解放出來,去做更有趣、更有創造性的探索性測試(exploratory testing)工作,就像agile testing: a practical guide for testers and agile teams [cg08]一書中闡述的那樣。

6.3.5 固件資料

手工測試一套系統的時候,為它提供真實資料非常有用,這樣你便如同在真實應用中使用系統。團隊從手工測試轉向自動測試的時候,你常常很想隻移植産品資料的一個子集,進而讓自動測試可以快速跟一個正常運作的系統互動。

一鍵式系統搭建

要避免因使用共享環境導緻的閃爍的場景,團隊需要一份腳本,做到隻需按鍵一點就可以從零開始建立一份新的系統執行個體。

如果系統使用資料庫,腳本産生的資料庫應包含最新的模式(schema),以及所有的存儲過程、視圖、函數等。它應當僅包含系統正常運轉所需的最少基礎資料,比如配置資料。任何其他資料都應該等各個獨立的場景自己去建立。

如果系統中有消息隊列,或者memcache守護程序,搭建腳本也要啟動它們,且使用你期望運作系統中應有的最低配置。

即使團隊不在産品代碼中使用ruby,他們也可以試着用ruby的activerecorda gem來管理資料庫模式和遷移腳本。activerecord能讓這類日常雜務變得輕而易舉。

另一種方法,讓每個測試建構自己的資料,看上去難度太大。在遺留系統中——特别是系統設計經不斷發展而來的那種,建立目前測試所需的單個對象都意味着為它建立所依賴的全部對象構成的一整棵大樹,你會覺得最友善的辦法就是在固件資料(fixture data)中把它一次性建立好,然後與其他測試共享這棵大樹。

這一方法有幾個嚴重的問題。一組固件資料,即使開始時相對精簡,時間長了體積也會隻增不減。随着越來越多的場景開始依賴這些資料,且每個場景都要對它們做一點特别的處理,固件資料的體積會越來越大,複雜度也會越來越高。當你為了某個場景對資料做點改動,結果卻使其他場景失敗時,你将開始感受到脆弱的特性的痛苦。有這麼多不同的場景依賴于固件資料,你會傾向于糊上更多的資料,因為這比修改已有的資料更加安全。某個場景失敗時,你很難清楚系統中的哪一團資料可能跟失敗相關,診斷起來也更加困難。

如果固件資料的集合體積龐大,在兩個測試之間準備這些資料就會變慢。這會給你帶來壓力,誘使你編寫滲露的場景,進而系統的狀态不會在兩個場景之間重置,因為這樣速度更快。但這樣做的後果我們前面已經解釋過了。

我們認為固件資料是一種反模式(antipattern)。我們更傾向于使用之前介紹過的測試資料構造器,比如factorygirl1,那樣可以讓測試本身從内部建立相關資料,而不是讓這些資料淹沒在一大團混亂的固件資料集合中。

每日建構

當你碰到由大量場景所導緻的緩慢的特性時,有必要考慮将建構拆成兩部分。用标簽來标記那些每次送出代碼時都應運作的場景,其他的都降為隻在每夜建構時運作。

這一模式的使用依賴于團隊願意承擔風險的程度,以及犯錯的趨向。降為每夜建構時運作的場景是那些極少失敗甚至不失敗的場景。它們針對的是那些幾個月都不發生改變的功能,覆寫的是那些目前沒有開發動作的穩定代碼。它們是不得已時你情願全部删除的場景。

為适當的場景打上針對簽入建構的标簽會帶來額外的維護成本。一段時間後,這些場景中有一部分将趨于穩定,這時應該把它們降低到每夜建構中,然後用新的場景替換它們。

雖然每夜建構可以作為擺脫困境的好方法,通常來說長期的正确方案還是會打破你的大泥球。

6.3.6 大量場景

看起來似乎是陳述常識,可是擁有大量的場景是最容易導緻特性整體運作緩慢的原因。我們并不是建議你放棄bdd并回到“牛仔式程式設計”(cowboy coding)當中,但我們真心建議你把緩慢的特性看做一個危險信号。除了需要很長時間等待回報,擁有大量的測試還有其他的不利影響。數量巨大的特性很難組織得井井有條,閱讀者也會覺得在其中前後翻閱極不友善。底層步驟定義和支援代碼的維護同樣變得更加困難。

我們發現,使用單一龐大建構的團隊也更傾向于使用一種最适合用“大泥球”來描述的架構。由于系統的全部行為都實作在一個地方,所有的測試也隻能放在一個地方,且隻能作為一個巨塊一起運作。在生存時間較長的ruby on rails應用中,這是一種典型的症狀,這樣的應用通常都經過長期持續的發展,各子系統間卻沒有清楚的接口。

我們會在下一節讨論更多關于如何處理“大泥球”的内容。正視這一問題,在端倪初現時一次性解決它至關重要,然而它并不是你在一夜之間就能解決的一個問題。

與此同時,你可以使用子檔案夾和标簽來保持特性的合理組織(參見第5章)。标簽對此好處尤多,因為你可以用标簽來拆分測試。你可以并發運作拆分後的不同測試集合,甚至把某些部分降為隻在每夜建構(參見6.3.5節)時運作。

還有一種方法值得考慮,對于你在cucumber場景中描述的行為,是否有一些可以下移一層,使用快速的單元測試來表達?熱情擁抱cucumber的團隊有時會忘了還有單元測試這回事,而過分地依賴緩慢的內建測試來擷取回報。試着将cucumber場景想象成向業務人員傳達代碼一般行為的粗略描述,同時仍然要靠快速的單元測試來獲得盡可能高的覆寫率。實作cucumber場景時讓測試人員和程式員結對工作,進而協助達成這一目标。系統的某種行為需要用緩慢的端到端cucumber場景來實作呢,還是應通過快速的單元測試來驅動呢?測試人員和程式員的結對可以對此做出恰當的決策。

6.3.7 大泥球

大泥球(big ball of mud)2是針對某一類軟體設計的一個具有諷刺意味的詞語,在這種軟體設計中,你實際上看不到任何人真正為軟體設計做點什麼。換句話說,整個軟體結構就是一大團亂麻。

我們已經解釋了大泥球會在cucumber測試中引發哪些問題——緩慢的特性、固件資料和共享的環境,這些都是它可能導緻的麻煩。要警惕這些信号并敢于對系統設計做出改變,進而使系統測試起來更加容易。

我們建議你把alistair cockburn的端口和擴充卡(ports and adapter)3架構作為設計系統的方法,進而使系統可測試。michael feathers的working effectively with legacy code [fea04]一書提供了很多實用的例子,教你如何把原先設計時沒有考慮測試的大型系統分解開來。

跟團隊定期召開讨論系統架構的會議:大家喜歡哪些方面,不喜歡哪些方面,又有哪些方面是大家願意接受的。大家總是容易被雄心勃勃的想法弄得情緒高漲,可回到座位上不久那些想法就很快消失在稀薄的空氣中,是以要確定讨論結束時盡量得出現實可行的步驟,進而沿着正确的方向推動大家的工作。

以上基本涵蓋了你在團隊中采用cucumber時可能遭遇的最常見的問題。然而,了解這些問題是一回事,抽出時間來處理它們則完全是另一回事了。下一節我們就來讨論一種幫你找到合适時間的重要技巧。