1. 程式人生 > >醒醒吧少年,只用Cucumber不能幫助你BDD

醒醒吧少年,只用Cucumber不能幫助你BDD

reat 另一個 sso 編碼 產生 || 多語 sed 依然

轉載:http://insights.thoughtworkers.org/bdd/

引言

在Ruby社區中,測試和BDD一直是被熱議的話題,不管是單元測試、集成測試還是功能測試,你總能找到能幫助你的工具,Cucumber就是被廣泛使用的工具之一。許多團隊選擇Cucumber的原因是“團隊要BDD”,也就是行為驅動開發(Behavior Driven Development),難道用了Cucumber之後團隊就真的BDD了麽?

事情當然沒這麽簡單了,BDD作為一種軟件開發方法論,一定要理解其含義並且遵循特定的流程,工具只不過是起輔助作用而已。會切菜的不一定都是廚子,會寫代碼的不一定都是程序員。Cucumber的作者Aslak也在博客中提到

在BDD出現的9年後,依然有不少團隊在使用BDD時出現問題……BDD依然經常被人誤解成單純的測試,或者是一個可以被下載的工具。

同時,Aslak也吐槽了Cucumber目前的處境

就在最近,Cucumber已經被下載了超過500萬次,我很高興它如此受歡迎,同時也為它被廣泛的誤用而感到失望……Cucumber有時依然被錯誤的當成自動化測試工具,而不是我當時創建的東西。

那麽問題來了,怎樣在日常項目中使用Cucumber呢?真的能在日常項目中進行BDD開發麽?要回答這個問題,我們需要重新認識一下BDD。

BDD的提出

2003年,開發人員Dan North偶然間發現把測試的標題經過簡單的文字處理可以更好表達代碼蘊含的業務邏輯,比如下面這段代碼,

public class CustomerLookupTest extends TestCase {
    testFindsCustomerById() {
        ...
    }
    testFailsForDuplicateCustomers() {
        ...
    }
}

當我們把測試方法中的test去掉,給單詞加上空格,然後把他們組合在一起時,就會出現:

CustomerLookup
 - finds customer by id
 - fails for duplicate customers
 - ...

在Dan看來,這無疑是對CustomerLookup類的描述,並且是用測試內容來描述代碼中類的行為。Dan發現他似乎找到了一種方式,可以在TDD的基礎上,通過測試來表達代碼的行為。在嘗到甜頭後,Dan寫了JBehave,用一個更關註代碼行為的工具來代替JUnit進行軟件開發。經過一番折騰後,Dan覺得只描述類行為不過癮,便開始把關註點從類擴展到整個軟件,他和當時項目組的業務人員一起把需求轉化成Given/When/Then的三段式,然後用JBehave寫成測試來描述軟件的某種行為。當測試完成後,開發人員才開始編碼,一旦測試通過,那軟件就完成了測試中描述的某種行為。在他看來,他把TDD升級了,因為他不再只關註於局部類的方法,而開始關註整個軟件的行為。

通過這種方式,Dan成功的把需求轉換成了軟件的功能測試,先寫功能測試再驅動出產品代碼,保證軟件行為正確性。其次,Dan強調在測試中要盡可能的使用業務詞匯,保證團隊成員對業務理解一致。於是,BDD就此誕生。

技術分享

BDD不只是自動化測試

在上面的故事中,“測試”這個詞出現了很多次,你是不是已經認為BDD就是用功能測試驅動產品代碼的開發流程呢?其實不然,功能測試只是一個結果而已,更重要的是和業務人員一起分析需求,溝通交流來產生測試的過程。用測試驅動出來的代碼可以保證是正確的,但如何保證測試是正確的呢?答案就是人,通過業務,開發和測試一起參與生成的測試文檔,不僅能保證軟件功能上是正確的,還能保證團隊成員對業務理解是一致的。在測試文檔中,也應該盡量保證使用自然語言和業務詞匯,減少非技術人員的學習成本。

在多年之後,Dan也終於給出了他對BDD的定義

BDD是第二代的、由外及內的、基於拉(Pull)的、多方利益相關者的(Stakeholder)、多種可擴展的、高自動化的敏捷方法。它描述了一個交互循環,可以具有帶有良好定義的輸出(即工作中交付的結果):已測試過的軟件。

Cucumber的另一位作者Matt Wynne也給出了自己的定義

BDD的實踐者們通過溝通交流,具體的示例和自動化測試幫助他們更好地探索、發現、定義並驅動出人們真正想用的軟件。

從上述定義我們可以看出,BDD更強調流程和一系列實踐,自動化測試只是其中一部分而已。

Cucumber到底怎麽用

理解了BDD的精髓後,我們就不難找出正確的使用Cucumber的方式了。根據Cucumber的定義,它的核心就是Specification,其實就是文檔化的需求。Specification是通過Requrement Workshop生成的,在Workshop中,業務、開發和測試一起分析需求,把需求用自然語言寫成文檔,然後再轉換成Given/When/Then的Specification文件,這樣便完成了BDD中最重要的一步--定義軟件正確的行為。接著開發人員開始編碼,完成相應需求,保證Specification文件運行通過,整個流程結束。

簡單來說,Cucumber其實不是一個自動化測試工具,而是一個促進團隊溝通合作的工具。但由於Cucumber無法確保上述流程真正的發生,有很多團隊簡化或者跳過了Workshop,直接開始寫Specification文件,沒有溝通就很難保證理解一致,Bug也許就在那時潛伏了下來。這樣大家也就不難理解作者吐槽的“Cucumber被廣泛的誤用”,其實Cucumber只是一個溝通工具,它只是剛巧可以運行測試而已。

技術分享

理想很豐滿,現實很骨感

任何工具和實踐都有優缺點,Cucumber也不例外。團隊在開始嘗試新的實踐或者工具時,多多少少都會碰到一些問題,下面我們就來看看一些使用Cucumber的問題。

沒有業務人員參與的Specification

要麽業務人員沒時間寫Specification,交給其他人寫,寫完之後業務人員也沒時間去審核。在這種情況下,很難保證Specification的業務正確性,一旦Specification出現問題,團隊可能出現理解不一致、甚至做錯需求的現象。反過來看,Specification文件由自然語言而不是代碼組成,也能反映出對非技術人員參與的重視程度。然而現實情況很難保證業務、測試、開發有充足的時間進行Specification的討論和編寫,這也是導致業務人員逐漸脫離Specification的主要原因。

Specification關註實現細節而不是業務邏輯

Cucumber使用自然語言描述業務需求,然而不少團隊都陷入到了實現細節中。比如

Scenario: Detect agent type based on contract number

Given I am on the ‘Find me‘ page
And I have entered a contract number
When I click ‘Continue‘ button
And a contact number match is found
Then the "Back" button will be displayed

上面的描述滿篇是點擊了哪個按鈕,輸入了什麽內容,看完之後反而讓人有點困惑,用戶到底為什麽要做這些,做了之後有什麽價值。這樣的Specification既不能滿足團隊成員對業務需求的了解,也會由於界面的細微改動運行失敗。

Step的嵌套調用

Specification文件由Step組成,在Step中我們可以通過Ruby進行自動化的頁面操作。有時我們會發現某些Specification會重復進行一系列的操作,這時我們就可以把重復的Step進行組合,創建出新的Step。比如這樣

Given there is student Harry
And there is professor Snape
And student Harry joins class of professor Snape

# use 1 new step instead of 3
Given student Harry in class of professor Snape

那麽這個新的Step該怎麽實現呢?Cucumber支持在Step中調用Step,比如這樣

Given /^student (.*) in class of professor (.*)/ 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}”一定會調用到你想要調用的Step麽?你無法確定在運行時,是否會出現另一個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在依賴它,這也加大了維護成本。

最後,嵌套次數過多的Step也會導致代碼復雜,難以理解。

Specification Report可讀性不高

Specification除了是自動化測試的描述文件之外,更重要的是它是軟件的“活文檔”。有時我們需要通過“活文檔”進行知識傳遞。Cucumber雖然提供生成Report的功能,但效果未免有些差強人意。比如下面

技術分享

滿篇綠色的Step,再加上Given/When/Then來搗亂,這樣的Report只是運行結果而已,可讀性很差,很難當成軟件需求文檔。究其原因,主要因為Cucumber Report的表現力差。

首先,它只支持純文本,在這個“一圖勝千言,無圖無真相”的時代很難只通過文字來描述復雜業務,如果能在文檔中加上圖片,甚至一段視頻,都會幫助我們更容易的理解復雜業務。比如像下面這樣的。

技術分享

其次,Cucumber Report關註的更多是Step,而不是軟件需求。當我們想到軟件文檔或者手冊時,我們腦海中想到的更多是像教科書一樣的文檔,內容之間有層級和關聯關系,每個功能有重點和概要內容,更偏向自然語言,而不是簡單的把Specification堆在一起,滿篇的Given/When/Then。在現實情況下,這樣的Cucumber Report也難免沒有人願意閱讀了。

改進措施

遇到上面的問題不要怕,我們只要理解問題本質,找到對策解決它,依然可以幫助我們更好地完成任務。下面我們就來嘗試解決一下上面提到的三個問題。

讓業務人員寫/審查Specification

對於上面的關註細節的例子,如果我們換一個思路,不去考慮UI之類的東西,就會得出更精煉的Specification。比如下面

Scenario: Customer has a tied agent policy 
so last name is required

Given I have a "TiedAgent" policy
When I submit my policy number
Then I should be asked for my last name

這樣的Specification不再關註按鈕,而關註具體的業務需求,這樣就把細節的UI操作推向了Cucumber Step的實現中。這樣可以更直接的展現需求,避免細節內容的幹擾。如果情況允許,我更支持讓業務人員寫Specification,或者最起碼也要審查Specification文件。通常業務人員是團隊中最不懂技術的,這反而是他們的優勢,可以把Specification變得更加面向需求,更加通俗易懂。

Step實現代碼的重用

我們可以通過重構Step實現代碼來進行有效的重用,比如下面

Given /^there is student (.*)/ do |student|
    ModelFactory.create_user(student)
end

Given /^there is professor (.*)/ do |professor|
    ModelFactory.create_user(professor)
end

Given /^student (.*) joins class of professor (.*)/ do |student, professor|
    ModelFactory.join_class(student, professor)
end

通過重構,我們抽象出ModelFactory進行相關數據準備,然後就可以重用ModelFactory實現新的Step

Given /^student (.*) in class of professor (.*)/ do |student, professor|
    ModelFactory.create_user(student, professor)
    ModelFactory.join_class(student, professor)
end

重用代碼而不是重用Step,這樣不僅可以讓Step的實現代碼更加簡潔,同時也避免了Step的嵌套調用。

擴展Cucumber生成高質量的文檔

Cucumber雖然自帶不少種格式的Report,但都不能稱其為真正的文檔。不過我們可以通過擴展Cucumber來生成高質量的文檔。

首先,我們可以使用Capybara在對某個正在執行的Step進行截圖。Capybara提供的截圖功能可以保留當前Step的運行狀態,通過圖片更容易理解當時的上下文,這些圖片拼在一起,其實就是一個完整的用戶操作流程。

其次,我們可以通過給Step添加詳細描述來解決Report不給力的問題。我們可以給每一個Specification文件創建一個相對應的描述文件,描述文件由兩部分組成,一部分是Step的標題,另一部分是詳細描述Step的內容。只要通過文本匹配就可以找到某個Step的詳細描述,再加上之前對Step的截圖,拼在一起就可以生成一個高質量的文檔了。

舉個例子,如果我們有這樣一個Specification文件

Scenario: Customer has a tied agent policy 
so last name is required

Given I have a "TiedAgent" policy
When I submit my policy number
Then I should be asked for my last name

創建一個對應的描述文件,文件類型是Markdown。

=====================
Given I have a "TiedAgent" policy
=====================
##Detail Information**
**In this step, you are assigned a "TieAgent" policy.**
![screenshot-1](./i-have-a-tied-agent-policy.png)
You can click [here](http://example.com) for more information
=====================
....

在上面的文件中,第一部分是Step標題,用來匹配Specification文件中的Step,第二部分是Markdown類型的片段,裏面有圖片、超鏈接等富文本元素,可以更好地幫助我們理解業務。

最後,通過Cucumber中提供的AfterStep Hook完成文檔的生成。比如這樣

AfterStep(‘@active-doc‘) do |scenario|
    @step ||= 0
    @doc ||= ActiveDocument.new
    @doc.generate(scenario, scenario.steps[@step].name)
    @step += 1
end

代碼中的ActiveDocument是自己實現的,它把豐富的HTML內容和截圖整合在一起,然後把Specification中所有的Step拼接在一起,就生成了一個Specification的文檔。這樣的文檔相比之前提到的Cucumber Report具有更高的可讀性,同時也具有更強的靈活性,因為文檔是通過HTML展現的,我們可以添加更多的內容,比如Specification文檔之間的跳轉鏈接,或者提前錄制的一段視頻放入文檔中,甚至可以加上第三方css和js庫讓文檔變得更加引人入勝。

技術分享

原來生活可以更美的

隨著BDD的發展,越來越多的工具進入了我們的視野。我們應該認清團隊的需求,結合團隊的特點選擇合適工具,不要盲目的隨大流。下面我來列舉一些具有代表性的工具,推薦給不同類型的團隊。

Cucumber

簡單來說,Cucumber實際上是一款有一定文檔性、可以幫助團隊溝通合作的、提供自動化測試功能的工具。特點是上手簡單、社區活躍、文檔表現力不足。所以如果團隊剛開始嘗試BDD,更看重自動化測試方面,而對需求文檔化要求不高,Cucumber是一個不錯的選擇。同時Cucumber目前支持Ruby, C#, JVM, JS和C++,眾多平臺也是一個加分項。

Concordion

與Cucumber相比,Concordion提供了更好的文檔支持。Concordion的Specification是HTML格式的,我們再也不用生搬硬套的使用Given/When/Then進行功能描述了。在HTML文件中,我們可以更加自由的描述業務需求,同時可以增加好看的樣式,添加更友好的交互,放入更多的視頻和圖片等等。

總而言之一句話,HTML比純文本更加靈活強大,適合閱讀。同時我們也要清楚HTML的學習和維護成本相比純文本更加昂貴,非技術的人可能很難單獨完成。和技術人員結對完成,或者在技術人員完成後進行審查也是一個不錯的選擇。但由於Concordion目前只對C#和JAVA支持較好,所以如果團隊剛好用到C#和Java,並且非常看重文檔化需求,那麽Concordion要比Cucumber更加適合你們。在下面的例子中,我們使用Concordion生成了“教學評估”相關的需求文檔,並且使用了shower.js增強了用戶交互,在保證軟件功能的同時,帶來了更好的閱讀體驗。

交互性更好地需求文檔,內容組織合理,閱讀體驗好。

技術分享

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

技術分享

Gauge

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

技術分享

總結一下

  1. BDD不是工具,而是一套流程和一系列實踐。它需要團隊成員的通力合作,可以幫助整個團隊更好的理解業務,理解軟件。
  2. Cucumber作為支持BDD的一種工具,不單單是自動化測試工具。在解決了Cucumber的一些問題後,團隊可以更加有效的使用。
  3. Cucumber、Concordion和Guage各有不同,選擇一款適合團隊自身需要的工具,也能保證團隊順利運作,少走彎路。

好了少年,我只能幫你到這裏了,接下來BDD之路就看你自己的了。

點擊這裏瀏覽演講視頻。

醒醒吧少年,只用Cucumber不能幫助你BDD