第一次看到Cucumber和BDD(Behavior Driven Development, 行為驅動開發),是在四年前。那時才開始工作,對軟件測試工具相當着迷。只要是開源的、免費的,我就一定要下載,安裝,試用。有的工具用途單一、好懂(如Jmeter,Watir);有的工具,則涉及到一些軟件領域的獨有概念,不好懂,(如STAF,Cucumber)。好懂的,我上手、試用、推廣,不亦樂乎;不好懂的,就只能丟在一邊,這里面就包括Cucumber。
再次看到Cucumber,已是兩年前。我對軟件開發的理解也深了些,這一看,可真是個好東西。之后我與Cucumber間發生的故事,稍后慢慢向大家交代。這開篇的第一章,我想獻給如當年的我一樣,偶然見到了Cucumber和BDD,卻不明所以將之丟在一邊的家伙們。
初聞Cucumber的人,第一件事一定是來到Cucumber的首頁,第一眼看到的一定是
很不幸的是,這六張圖不太好懂。因為它們按照BDD的流程來編寫的。為了讓它們好懂些,我們拋開BDD,采用傳統的軟件開發模型(設計->編碼->測試)來看它。傳統流程如下:
圖1,這是一個被測系統——用ruby編寫的計算器。
為了便於大家理解,我試着修改了一些。
class Calculator def push(n) #記數 @args ||= [] #初始化空數組 @args << n end def sum() #返回所有數字和 sum = 0 @args.each do |i| sum += i end @result = sum end def result @result end end
計算器Calculator提供兩個功能: 記數push;加和sum。push將數字一一記錄在@args數組中;sum則將所有@args數組中的數字累加得和,存入@result中。寫完了被測系統,我們來編寫測試用例。
圖2,這是為了測試上述計算器,使用Cucumber描述的測試用例。
原圖中的英文描述,被我翻譯成了中文。:
Feature: 計算器 Scenario: 兩數相加 Given 我有一個計算器 And 我向計算器輸入50 And 我向計算器輸入70 When 我點擊累加 Then 我應該看到結果120
支持中、英等自然語言,是Cucumber的特點之一。在Cucumber的幫助文檔里,聲明它支持包括簡體中文、繁體中文、日文、韓文和英文在內的45種語言。
注意:我並未將所有英文都翻譯成中文,而是留下了幾個關鍵字:
- Feature(功能)
- Scenario(情景)
- Given(給定)
- And(和)
- When(當)
- Then(則)
它們的含義與原有自動化測試工具中的概念相同,類比如下:
Cucumber | Unit Test |
---|---|
Feature (功能) | test suite (測試用例集) |
Scenario(情景) | test case (測試用例) |
Given(給定) | setup(創建測試所需環境) |
When(當) | test(觸發被測事件) |
Then(則) | assert(斷言,驗證結果) |
Cucumber放棄了原有的關鍵字,而選擇了左邊五種,只是為了更加流暢地支持自然語言。使用Cucumber的關鍵字,創建了測試用例,接下來,要如何使用Cucumber來運行它呢?
圖3,這是運行Cucumber時的畫面。
在一台安裝好Cucumber的機器上,運行上述測試用例,便可以看到下列輸出:
Feature: 計算器 Scenario: 兩數相加 # features/calculator.feature:3 Given 我有一個計算器 # features/calculator.feature:4 And 我向計算器輸入50 # features/calculator.feature:5 And 我向計算器輸入70 # features/calculator.feature:6 When 我點擊累加 # features/calculator.feature:7 Then 我應該看到結果120 # features/calculator.feature:8 1 scenario (1 undefined) 5 steps (5 undefined) 0m0.005s You can implement step definitions for undefined steps with these snippets: Given /^我有一個計算器$/ do pending # express the regexp above with the code you wish you had end Given /^我向計算器輸入(\d+)$/ do |arg1| pending # express the regexp above with the code you wish you had end When /^我點擊累加$/ do pending # express the regexp above with the code you wish you had end Then /^我應該看到結果(\d+)$/ do |arg1| pending # express the regexp above with the code you wish you had end
Cucumber首先輸出的是測試用例的描述,然后3行總結性地輸出:本功能(Feature)有1個情景(1 scenario);5個步驟(5 steps),全部5個步驟均未定義(undefined);運行耗時0.005秒。這里出現了兩個新名詞:步驟(steps)和步驟定義(step definitions)。在Cucumber中,以關鍵字Given, And, When, Then開頭的每一行,為一個步驟。在兩數相加的情景中,一共有5行。因此,結果顯示:5個步驟。
如何定義一個步驟,在Cucumber的運行結果中也給出了詳細的辦法。在3行總結性輸出后,緊接着便是:You can implement…即:你可以使用下面的代碼段實現步驟定義,然后是4個小的代碼段。這些代碼段,便是Cucumber依照情境中我們使用的5個步驟,幫助我們生成的步驟定義框架。每個框架都將內容部分空白出來,等待填充。下面,我們來進行步驟定義。
圖4, 這是一個步驟定義的代碼示范。
我們依照圖2的樣子,向中文步驟中填入代碼,如下:
Given /^我有一個計算器$/ do @c = Calculator.new end Given /^我向計算器輸入(\d+)$/ do |num| @c.push(num.to_i) end When /^我點擊累加$/ do @c.sum end Then /^我應該看到結果(\d+)$/ do |result| @c.result.should == result.to_i end
步驟定義的過程,就是向代碼段——步驟定義框架——中填入代碼的過程,即:用代碼來描述你期望的,該步驟應該執行的動作。完整的步驟定義是一個函數,它:
- 以正則表達式作為函數名
- 匹配值作為參數
- 以測試人員輸入的代碼作為內容
因為有了正則表達式的匹配,5個步驟僅需要4個步驟定義。“我向計算器輸入50、70”兩個步驟,都可以用“我向計算器輸入(\d+)”一個正則表達式來描述。匹配值被自動提取出來作為參數,傳入代碼。注意:所有匹配值,即參數,都是以字符串的形式傳遞,因此,我加入了num.to_i 與 result.to_i,將得到的字符串轉為整形。步驟定義完成,再次執行Cucumber。屏幕將會顯示一片綠色。
圖5,它是一個執行Cucumber測試用例,並成功通過的畫面。
步驟定義完成后,再次運行Cucumber。Cucumber會找到步驟定義,並按照其代碼去執行。結果如下:
Feature: 加法 Scenario: 兩數相加 # features/calculator.feature:3 Given 我有一個計算器 # features/step_definitions/a.rb:2 And 我向計算器輸入50 # features/step_definitions/a.rb:6 And 我向計算器輸入70 # features/step_definitions/a.rb:6 When 我點擊累加 # features/step_definitions/a.rb:10 Then 我應該看到結果120 # features/step_definitions/a.rb:14 1 scenario (1 passed) 5 steps (5 passed) 0m0.003s
步驟定義被我保存在文件夾step_definitions下的a.rb當中。步驟定義所在文件與起始行數,被打印在每個步驟結尾,以方便查找和修改。最后,Cucumber總結性地輸出運行結果:1個情景,5個步驟,全部通過。
圖6, 這是一個執行Cucumber測試用例,但失敗的畫面。
為了讓這個已經十分簡單的計算器產生bug,我只好將它改錯為:
class Calculator def sum() sum = 0 @args.each do |n| sum = n #此處原為:sum += n end @result = sum end end
再次運行Cucumber,結果為:
Feature: 加法 Scenario: 兩數相加 # features/calculator.feature:3 Given 我有一個計算器 # features/step_definitions/a.rb:2 And 我向計算器輸入50 # features/step_definitions/a.rb:6 And 我向計算器輸入70 # features/step_definitions/a.rb:6 When 我點擊累加 # features/step_definitions/a.rb:10 Then 我應該看到結果120 # features/step_definitions/a.rb:14 expected: 120 got: 70 (using ==) (RSpec::Expectations::ExpectationNotMetError) ./features/step_definitions/a.rb:15:in `/^我應該看到結果(\d+)$/' features/calculator.feature:8:in `Then 我應該看到結果120' Failing Scenarios: cucumber features/calculator.feature:3 # Scenario: 兩數相加 1 scenario (1 failed) 5 steps (1 failed, 4 passed) 0m0.004s
失敗的步驟是用紅色標示出來的。在最后一個步驟中,Cucumber期待的結果為120,但得到的是70。注意:失敗的情景列表(Failing Scenarios)里列出的是:“兩數相加”這個情景所在的文件與起始行數。這是因為一個功能文件內,可能含有多個情景,這種輸出可以便於找到出錯的情景。
接下來的總結性結果為:1個情景失敗(1 failed),5個步驟中,4個通過,1個失敗。
作為自動化測試工具的Cucumber,就介紹到這里。
在繼續之前,我們先回顧一下本章內容。
回顧:
- Cucumber是一個自動化測試工具
- 它提供了自然語言的支持,我們可以用自然語言描述、並執行測試用例
- 它提供了自然語言與代碼的銜接,通過步驟與步驟定義
- 它提供了自然語言對代碼的調用,當步驟定義結束后,執行Cucumber,它會自動調用步驟定義內的代碼運行
- 它提供了良好的斷言(assert)機制。當執行失敗時,我們可以看到完成的測試用例,以及明確的失敗原因。