摘自:http://www.51testing.com/html/87/300987-3708330.html
引言
在Ruby社區(qū)中,測試和BDD一直是一個被熱議的話題,不管是單元測試,集成測試和功能測試,你總能找到能幫助你的工具,Cucumber就是被廣泛使用的工具之一。許多團隊選擇Cucumber的原因是“團隊要BDD”,也就是行為驅(qū)動開發(fā)(Behavior. Driven Development),難道用了Cucumber之后團隊就真的BDD了么?

事情當然沒這么簡單了,BDD作為一種軟件開發(fā)方法論,一定要理解其含義并且遵循特定的流程,工具只不過是起輔助作用而已。會切菜的不一定都是廚子,會寫代碼的不一定都是程序員。近期Cucumber的作者Aslak也在博客中提到
在BDD出現(xiàn)的9年后,依然有不少團隊在使用BDD時出現(xiàn)問題……BDD依然經(jīng)常被人誤解成單純的測試,或者是一個可以被下載的工具。
同時,Aslak也吐槽了Cucumber目前的處境
就在最近,Cucumber已經(jīng)被下載了超過500萬次,我很高興它如此受歡迎,同時也為它被廣泛的誤用而感到失望……Cucumber有時依然被錯誤的當成了自動化測試工具,而不是我當時創(chuàng)建的東西。
那么問題來了,怎樣在日常項目中使用Cucumber呢?真的能在日常項目中進行BDD開發(fā)么?要回答這個問題,我們需要重新認識一下BDD。
BDD的提出
2003年,開發(fā)人員Dan North偶然間發(fā)現(xiàn)把測試的標題經(jīng)過簡單的文字處理可以更好表達代碼蘊含的業(yè)務(wù)邏輯,比如下面這段代碼,
publicclassCustomerLookupTestextendsTestCase{? ? testFindsCustomerById() {? ? ? ? ...? ? }? ? testFailsForDuplicateCustomers() {? ? ? ? ...? ? }}
當我們把測試方法中的test去掉,給單詞加上空格,然后把他們組合在一起時,就會出現(xiàn):
CustomerLookup - finds customerbyid - failsforduplicate customers - ...
在Dan看來,這無疑是對CustomerLookup類的描述,并且是用測試內(nèi)容來描述代碼中類的行為。Dan發(fā)現(xiàn)他似乎找到了一種方式,可以在TDD的基礎(chǔ)上,通過測試來表達代碼的行為。在嘗到甜頭后,Dan寫了JBehave,一個更關(guān)注代碼行為的工具來代替JUnit進行軟件開發(fā)。經(jīng)過一番折騰后,Dan覺得只描述類行為不過癮,便開始把關(guān)注點從類擴展到整個軟件,他和當時項目組的業(yè)務(wù)人員一起把需求轉(zhuǎn)化成Given/When/Then的三段式,然后用JBehave寫成測試來描述軟件的某種行為。當測試完成后,開發(fā)人員才開始編碼,一旦測試通過,那軟件就完成了測試中描述的某種行為。在他看來,他把TDD升級了,因為他不再只關(guān)注于局部類的方法,而開始關(guān)注整個軟件的行為。
通過這種方式,Dan成功的把需求轉(zhuǎn)換成了軟件的功能測試,先寫功能測試再驅(qū)動出產(chǎn)品代碼,保證軟件行為正確性。其次,Dan強調(diào)在測試中要盡可能的使用業(yè)務(wù)詞匯,保證團隊成員對業(yè)務(wù)理解一致。于是,BDD就此誕生。

BDD不只是自動化測試
在上面的故事中,“測試”這個詞出現(xiàn)了很多次,你是不是已經(jīng)認為BDD就是用功能測試驅(qū)動產(chǎn)品代碼的開發(fā)流程呢?其實不然,功能測試只是一個結(jié)果而已,更重要的是和業(yè)務(wù)人員一起分析需求,溝通交流來產(chǎn)生測試的過程。用測試驅(qū)動出來的代碼可以保證是正確的,但如何保證測試是正確的呢?答案就是人,通過業(yè)務(wù),開發(fā)和測試一起參與生成的測試文檔,不僅能保證軟件功能上是正確的,還能保證團隊成員對業(yè)務(wù)理解是一致的。在測試文檔中,也應(yīng)該盡量保證使用自然語言和業(yè)務(wù)詞匯,減少非技術(shù)人員的學習成本。
在多年之后,Dan也終于給出了他對BDD的定義
BDD是第二代的、由外及內(nèi)的、基于拉(Pull)的、多方利益相關(guān)者的(Stakeholder)、多種可擴展的、高自動化的敏捷方法。它描述了一個交互循環(huán),可以具有帶有良好定義的輸出(即工作中交付的結(jié)果):已測試過的軟件。
Cucumber的另一位作者Matt Wynne也給出了自己的定義
BDD的實踐者們通過溝通交流,具體的示例和自動化測試幫助他們更好地探索,發(fā)現(xiàn),定義并驅(qū)動出人們真正想用的軟件
從上述定義我們可以看出,BDD更強調(diào)流程和一系列實踐,自動化測試只是其中一部分而已。
Cucumber到底怎么用
理解了BDD的精髓后,我們就不難找出正確的使用Cucumber的方式了。根據(jù)Cucumber的定義,它的核心就是Specification,其實就是文檔化的需求。Specification是通過Requrement Workshop生成的,在Workshop中業(yè)務(wù),開發(fā)和測試一起分析需求,把需求用自然語言寫成文檔,然后再轉(zhuǎn)換成Given/When/Then的Specification文件,這樣便完成了BDD中最重要的一步,定義軟件正確的行為。接著開發(fā)人員開始編碼,完成相應(yīng)需求,保證Specification文件運行通過,整個流程結(jié)束。
簡單來說,Cucumber其實不是一個自動化測試工具,而是一個促進團隊溝通合作的工具。但由于Cucumber無法確保上述流程真正的發(fā)生,有很多團隊簡化或者跳過了Workshop,直接開始寫Specification文件,沒有溝通就很難保證理解一致,Bug也許就在那時潛伏了下來。這樣大家也就不難理解作者吐槽的“Cucumber被廣泛的誤用”,其實Cucumber只是一個溝通工具,它只是剛巧可以運行測試而已。

理想很豐滿,現(xiàn)實很骨感
任何工具和實踐都有優(yōu)缺點,Cucumber也不例外。團隊在開始嘗試新的實踐或者工具時,多多少少都會碰到一些問題,下面我們就來看看一些使用Cucumber的問題。
沒有業(yè)務(wù)人員參與的Specification
要么業(yè)務(wù)人員沒時間寫Specification,交給其他人寫,寫完之后業(yè)務(wù)人員也沒時間去審核。在這種情況下,很難保證Specification的業(yè)務(wù)正確性,一旦Specification出現(xiàn)問題,團隊可能發(fā)生理解不一致,甚至做錯需求的現(xiàn)象。反過來看,Specification文件由自然語言而不是代碼組成,也能反映出對非技術(shù)人員參與的重視程度。然而現(xiàn)實情況很難保證業(yè)務(wù)、測試、開發(fā)有充足的時間進行Specification的討論和編寫,這也是導致業(yè)務(wù)人員逐漸脫離Specification的主要原因。
Specification關(guān)注實現(xiàn)細節(jié)而不是業(yè)務(wù)邏輯
Cucumber使用自然語言描述業(yè)務(wù)需求,然而不少團隊都陷入到了實現(xiàn)細節(jié)中。比如
Scenario:Detect agent type based on contract numberGivenI amonthe'Find me'pageAnd I have entered a contract numberWhen I click'Continue'buttonAnd a contact number match is foundThen the"Back"button will be displayed
上面的描述滿篇是點擊了那個按鈕,輸入了什么內(nèi)容,看完之后反而讓人有點困惑,用戶到底為什么要做這些,做了之后有什么價值。這樣的Specification既不能滿足團隊成員對業(yè)務(wù)需求的了解,也會由于界面的細微改動運行失敗。
Step的嵌套調(diào)用
Specification文件由Step組成,在Step中我們可以通過Ruby進行自動化的頁面操作。有時我們會發(fā)現(xiàn)某些Specification會重復進行一系列的操作,這時我們就可以把重復的Step進行組合,創(chuàng)建出新的Step。比如這樣
Given thereisstudent HarryAnd thereisprofessor SnapeAnd student Harry joinsclassofprofessorSnape# use 1 new step instead of 3Given student HarryinclassofprofessorSnape
那么這個新的Step該怎么實現(xiàn)呢?Cucumber支持在Step中調(diào)用Step,比如這樣
Given/^student (.*)inclassofprofessor(.*)/do|student,professor|step"there is student#{student}"step"there is professor#{professor}"step"student#{student}joins class of professor#{professor}"end
乍一看好像沒什么問題,其實不然。Step使用正則表達式進行匹配,問題恰恰出在正則上。
首先,正則靈活性很大,你確定上面例子中step “there is student #{student}”一定會調(diào)用到你想要調(diào)用的Step么?你無法確定在運行時,是否會出現(xiàn)另一個Step “there is student come from China”來截胡。
其次,正則逆推難度很大,也就是說當你看到“^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]).(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0).(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0).(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$”時,你很難看出這是在匹配IP地址。所以當我們需要修改step時,很難確定有多少個step在依賴它,這也加大了維護成本。
最后,嵌套次數(shù)過多的Step也會導致代碼復雜,難以理解。
Specification Report可讀性不高
Specification除了是自動化測試的描述文件之外,更重要的是軟件的“活文檔”。有時我們需要通過“活文檔”進行知識傳遞。Cucumber雖然提供生成Report功能,但效果未免有些差強人意。比如下面

滿篇綠色的Step,再加上Given/When/Then來搗亂,這樣的Report只是運行結(jié)果而已,可讀性很差,很難當成軟件需求文檔。究其原因,主要因為Cucumber Report的表現(xiàn)力差。
首先,它只支持純文本,在這個“一圖勝千言,無圖無真相”的時代很難只通過文字來描述復雜業(yè)務(wù),如果能在文檔中加上圖片,甚至一段視頻,都會幫助我們更容易的理解復雜業(yè)務(wù)。比如像下面這樣的。

其次,Cucumber Report關(guān)注的更多是Step,而不是軟件需求。當我們想到軟件文檔或者手冊時,我們腦海中想到的更多是像教科書一樣的文檔,內(nèi)容之間有層級和關(guān)聯(lián)關(guān)系,每個功能有重點和概要內(nèi)容,更偏向自然語言,而不是簡單的把Specification堆在一起,滿篇的Given/When/Then。在現(xiàn)實情況下,這樣的Cucumber Report也難免沒有人愿意閱讀了。
改進措施
遇到上面的問題不要怕,我們只要理解問題本質(zhì),找到對策解決它,依然可以幫助我們更好地完成任務(wù)。下面我們就來嘗試解決一下上面提到的三個問題。
讓業(yè)務(wù)人員寫/審查Specification
對于上面的關(guān)注細節(jié)的例子,如果我們換一個思路,不去考慮UI之類的東西,就會得出更精煉的Specification。比如下面
Scenario: Customer has atiedagent policy solastname is requiredGiven I have a"TiedAgent"policyWhen I submitmypolicy numberThen I should be askedformylastname
這樣的Specification不再關(guān)注按鈕,而是具體的業(yè)務(wù)需求,這樣就把細節(jié)的UI操作推向了Cucumber Step的實現(xiàn)中。這樣可以更直接的展現(xiàn)需求,避免細節(jié)內(nèi)容的干擾。如果情況允許,我更支持讓業(yè)務(wù)人員寫Specification,或者最起碼也要審查Specification文件。通常業(yè)務(wù)人員是團隊中最不懂技術(shù)的,這反而是他們的優(yōu)勢,可以把Specification變得更加面向需求,更加通俗易懂。
Step實現(xiàn)代碼的重用
我們可以通過重構(gòu)Step實現(xiàn)代碼來進行有效的重用,比如下面
Given /^there is student (.*)/do|student|? ? ModelFactory.create_user(student)endGiven /^thereisprofessor (.*)/do|professor|? ? ModelFactory.create_user(professor)endGiven /^student (.*) joins classofprofessor (.*)/do|student, professor|? ? ModelFactory.join_class(student, professor)end
通過重構(gòu),我們抽象出ModelFactory進行相關(guān)數(shù)據(jù)準備,然后就可以重用ModelFactory實現(xiàn)新的Step
Given/^student (.*)inclassofprofessor(.*)/do|student,professor|ModelFactory.create_user(student, professor)ModelFactory.join_class(student, professor)end
重用代碼而不是重用Step,這樣不僅可以讓Step的實現(xiàn)代碼更加簡潔,同時也避免了Step的嵌套調(diào)用。
擴展Cucumber生成高質(zhì)量的文檔
Cucumber雖然自帶不少種格式的Report,但都不能稱其為真正的文檔。不過我們可以通過擴展Cucumber來生成高質(zhì)量的文檔。
首先,我們可以使用Capybara在對某個正在執(zhí)行的Step進行截圖。Capybara提供的截圖功能可以保留當前Step的運行狀態(tài),通過圖片更容易理解當時的上下文,這些圖片拼在一起,其實就是一個完整的用戶操作流程。
其次,我們可以通過給Step添加詳細描述來解決Report不給力的問題。我們可以給每一個Specification文件創(chuàng)建一個相對應(yīng)的描述文件,描述文件由兩部分組成,一部分是Step的標題,另一部分是詳細描述Step的內(nèi)容。只要通過文本匹配就可以找到某個Step的詳細描述,再加上之前對Step的截圖,拼在一起就可以生成一個高質(zhì)量的文檔了。
舉個例子,如果我們有這樣一個Specification文件
Scenario: Customer has atiedagent policy solastname is requiredGiven I have a"TiedAgent"policyWhen I submitmypolicy numberThen I should be askedformylastname
創(chuàng)建一個對應(yīng)的描述文件,文件類型是Markdown。
=====================Given I have a "TiedAgent" policy
=====================##Detail Information****In this step, you are assigned a "TieAgent" policy.**You can click [here](http://example.com) for more information
=====================....
在上面的文件中,第一部分是Step標題,用來匹配Specification文件中的Step,第二部分是Markdown類型的片段,里面有圖片,超鏈接等富文本元素,可以更好地幫助我們理解業(yè)務(wù)。
最后,通過Cucumber中提供的AfterStep Hook完成文檔的生成。比如這樣
AfterStep('@active-doc')do|scenario|@step||=0@doc||= ActiveDocument.new@doc.generate(scenario, scenario.steps[@step].name)@step+=1end
代碼中的ActiveDocument是自己實現(xiàn)的,它把豐富的HTML內(nèi)容和截圖整合在一起,然后把Specification中所有的Step拼接在一起,就生成了一個Specification的文檔。這樣的文檔相比之前提到的Cucumber Report具有更高的可讀性,同時也具有更強的靈活性,因為文檔是通過HTML展現(xiàn)的,我們可以添加更多的內(nèi)容,比如Specification文檔之間的跳轉(zhuǎn)鏈接,或者提前錄制的一段視頻放入文檔中,甚至可以加上第三方css和js庫讓文檔變得更加引人入勝。

原來生活可以更美的
隨著BDD的發(fā)展,越來越多的工具進入了我們的視野。我們應(yīng)該認清團隊的需求,結(jié)合團隊的特點選擇合適工具,不要盲目的隨大流。下面我來列舉一些具有代表性的工具,推薦給不同類型的團隊。
Cucumber
簡單來說,Cucumber實際上是一款有一定文檔性,可以幫助團隊溝通合作的,提供自動化測試功能的工具。特點是上手簡單,社區(qū)活躍,文檔表現(xiàn)力不足。所以如果團隊剛開始嘗試BDD,更看重在自動化測試方面,而對需求文檔化要求不高時,Cucumber是一個不錯的選擇。同時Cucumber目前支持Ruby, C#, JVM, JS和C++,眾多平臺也是一個加分項。
Concordion
與Cucumber相比,Concordion提供了更好的文檔支持。Concordion的Specification是HTML格式的,我們再也不用生搬硬套的使用Given/When/Then進行功能描述了。在HTML文件中,我們可以更加自由的描述業(yè)務(wù)需求,同時可以增加好看的樣式,添加更友好的交互,放入更多的視頻和圖片等等??偠灾痪湓?,HTML比純文本更加靈活強大,適合閱讀。同時我們也要清楚HTML的學習和維護成本相比純文本更加昂貴,非技術(shù)的人可能很難單獨完成。和技術(shù)人員結(jié)對完成,或者在技術(shù)人員完成后進行審查也是一個不錯的選擇。但由于Concordion目前只對C#和JAVA支持較好,所以如果團隊剛好用到C#和Java,并且非??粗匚臋n化需求,那么Concordion要比Cucumber更加適合你們。在下面的例子中,我們使用Concordion生成了“教學評估”相關(guān)的需求文檔,并且使用了shower.js增強了用戶交互,在保證軟件功能的同時,帶來了更好的閱讀體驗。
交互性更好地需求文檔,內(nèi)容組織合理,閱讀體驗好。

其中一個需求的詳細描述,同時也是自動化測試。

Gauge
Gauge也在文檔方面進行了改善,Gauge的Specification文件由Markdown組成,相對純文本有了一定程度的提升,但還是不如Concordion靈活。Gauge使用Go編寫,天然支持并發(fā)運行,相比之下性能要更加有優(yōu)勢。同時Gauge支持多語言實現(xiàn),目前支持Java,C#和Ruby,相比Cucumber在跨平臺式需要整個切換工具,Gauge更容易做跨平臺。雖然目前Gauge處在開發(fā)階段,但依然值得關(guān)注。