BDD SpecFlow

使用 SpecFlow 從需求起步進行開發

陳志澤 2016/10/21 09:26:32
1742







主題

使用 SpecFlow 從需求起步進行開發

文章簡介

使用 SpecFlow 讓我們從實際需求情境起步,自然而然將測試開發融為一體地實現 TDD 精神

作者

陳志澤

版本/產出日期

V1.0/2016.10.20




1. 前言

前些日子參加公司內訓,講者就是在TDD方面相當有研究的91哥,上完三天課讓我對於TDD有更完整深層的認識及體會,當然這些認識體會並不代表實務上就可以順利導入,其中的心法及如何讓自己寫出具有可測試性的程式,以及面對 Legacy Code 該如何調整使其具有可測試性,這些都是需要在真實戰場中不斷思考及衝撞下才會有的經驗值,而筆者也努力累積相關經驗值中;說了這麼多,其實這篇不講 TDD 而是把重點放在初 BDD 開發模式,體驗一下如何使用 SpecFlow 讓我們從實際需求情境起步,自然而然將測試開發融為一體地實現 TDD 精神。


2. 環境

本文章建立於以下版本的測試環境:

Visual Studio 2015

SpecFlow 2.1.0


3. 安裝

點選擴充功能和更新,搜尋 SpecFlow 就可以找到對應版本的擴充功能,點選下載安裝。


在測試專案中從 Nuget 下載 SpecFlow 套件至專案中


由於 SpecFlow 預設 Unit Test Provider 是 NUnit Test,本文使用 MS Test 就需要開啟 app.config 進行調整


<unitTestProvider name="MsTest"></unitTestProvider>


 


4. 實際演練


建立需求功能與場景


我們可以在測試專案中建立 Features 資料夾,接著加入新項目選擇 SpecFlow Feature File 後,新增一個副檔名為 *.feature (與對應 *.cs 檔) 的需求功能檔案



首先會以 User Story 方式建立 Feature 的作用,並於下方清楚描述場景(Scenario)及需求規格(input & output);其中 Scenario 中每句紫色文字都表示一個步驟,而紫色表示尚未對應到步驟定義(Step Definitions)代碼,因此會在下個步驟中自動產出


1. Feature: 功能標題與描述 

2. Tag: 可訂於 Feature 或 Scenario 層級中,並且有繼承效果

3. Scenario: 測試情境,包含許多步驟 (Step)



簡單看一下這個 Scenario 包含了輸入條件、執行方式及預期結果,此情境表示當我輸入特定起始日期、結束日期及國定假日清單後(Given),呼叫 WorkingDayCounter 來計算工作日(When),預期會得到 5 天工作日的計算結果(Then),就如同測試的 3A (Arrange, Act and Assert)原則,透過這些白話文讓測試情境更加清晰

 


傳入條件可以依照需求調整型態,以下簡單介紹兩種常用的輸入方式:


傳入單筆參數


要傳入的資料就直接寫在步驟文字中,如果是數字會自動被識別作為此步驟的傳入參數;另外如果是要傳入文字就使用雙引 "some string" 標示,也會被自動識別。但是若是其他輸入資料像是 Date 格式時,就無法被正確識別,但沒關係就先用字串方式填上就對了,我們會在下步驟進行微調即可。


傳入多筆參數 (資料表)


如果有多筆資料可以使用表格方式呈現,使用 | 分隔符號分開各個欄位,第一列會輸入欄位名稱,此種型態的輸入參數會以 Table 物件傳遞,而後續可以透過 Table.CreateSet<MyClass>() 方式轉為 IEnumerable 泛型類型,讓我們可以方便操作強型別物件進行後續行為。

 


自動產出步驟定義 (Step Definitions)


這部分會透過先前定義的情境步驟轉化為實際執行的步驟,簡單來說就是點選 Generate Step Definitions 產出各執行步驟的空殼,讓我們自行去實作各步驟需要測試目標物件所執行的行為,藉此驗證測試目標的行為結果是否與需求一致。



產出後查看一下 Feature 檔,原本沒有定義的部分(紫色)都變白色文字(有定義),而此時最需要關注的是輸入參數是否如預期被剖析出來,因為SepcFlow 會自動判別數字、字串或資料表作為輸入參數(灰色文字部分),但大家都知道自動產生的東西一定沒有辦法滿足所有情況,所以若是無法滿足個人需求的話,就需要自行調整標籤中的 Regular Expression 文字,來正確剖析出各個 Step 描述中標示為輸入參數的位置。


調整 DateTime 格式


由於剛剛為了簡單讓工具幫我們產出 Step Definition 代碼,因此把日期先用字串標示,但我們真正需要的是 DateTime 格式的輸入參數阿!別擔心我們可以透過 Specflow 針對參數提供的自動轉型功能來進行微調,而調整的方式相當直覺,就是使用 Regular Expression 來定義文字 Petten 識別變數,因此作了以下的調整


1. CountWorkingDay.feature中的 2016/10/07 的雙引號移除 (不再以字串方式輸入)

2. 調整 Given 中的 regex 字串,移除輸入參數的雙引號

3. 最後將輸入型態改為 DateTime 後,剩下就交給 Specflow 來將輸入參數自動轉型了


調整 Step Definition 的 Binding Scope


特別注意由於 Step Definition 的 Binding 是全域的,也就是可以大家共用相同 Patten 的 Step Definition,因此也會帶來不同 Feature 間相同 Step Patten 的衝突,所以如果要限制 Step Definitions 的 Binding 範圍,可以透過 Scope 標籤限制哪個 Feature, Scenario, Tag 範圍,這樣就不會互相干擾;而 Scope 標籤可以設定在類別或方法上,表示個別限制類別或方法的 Binding 範圍


以下就是明確定義這個 Step Definition 類別的 Binding 範圍,只針對名稱為 Count Working Day 這個 Feature 而已 


 


實作步驟內容及執行測試


這個部分就如同 TDD 的操演方式,在實作各步驟的同時也就像是在寫測試 3A,因此不需急著實作出具體的邏輯代碼,而是先依物件導向設計思路寫出所需類別、屬性及方法後,接著善用智能標籤快速鍵 (Ctrl + .) 自動產出對應類別屬性方法代碼,勾勒完程式空殼後先來執行測試得到個紅燈(因尚未實作任何邏輯),接著就可使盡洪荒之力實作出恰好符合我們所需情境下的程式邏輯,此時只要確認執行測試後得到綠燈,這一回合就結束了;當然因為有測試的保護,我們也可以很放心地繼續精進(重構)代碼囉!


善用工具自動產出需要的類別及其屬性與方法代碼




完成後的 Step Definition 如下



其中由於 Given國定假日 方法是傳入 Table 型態,需要將他轉形成我們所需之物件,而 TechTalk.SpecFlow.Assist 提供 Table 的擴充方法,方便我們執行轉型任務如下


主程式空殼如下



執行測試取得紅燈



完成符合我們所需要情境下的程式邏輯後



執行測試取得綠燈(收工)


 


建立新的測試情境


就是因為需求存在著許多不同的情境條件,才會容易造成開發人員有所疏漏,因此如果需要加入新的測試情境,我們只需要加入一段新的 Scenario 即可。


編譯後就會出現對應的測試案例於測試總管中


直接就可以執行測試看看我們的程式邏輯有沒有滿足這個測試情境


 


使用 Background 集中共同執行步驟


大家有沒有發現有重複的步驟資料不斷的出現在各個 Scenario 中,感覺非常冗長。



我們可以把這些步驟透過 Background 集中起來,讓測試每情境時都會先執行這個共同步驟。



測試一下功效果然相同,測試依舊綠燈

 


使用 Scenario Outline 重複驅動測試


最後大家應該會發現,所有情境的Step組成都是一樣的,差別只有在輸入值的不同,這樣繼續複製下去相當冗長。



可以透過 Scenario Outline 來消除所有重複的測試情境;我們可以透過一個測試資料集合,定義不同輸入輸出值後,就可以重複執行不同邊界值下的測試情境,這樣是不是優雅多了!



編譯後測試案例出現在測試總管中



執行看看,達到一樣的效果,順利通過測試


 


使用 BeforeScenario 及 AfterScenario 處理測試前後事件


當在測試前後需要執行某些動作時,可以透過 BeforeScenarioAfterScenario 標籤對自訂 Method 進行定義;如下例就是在執行 Scenario 測試前要建立測試目標實體



5. 後記

本文實際演練如何透過 SpecFlow 實現 BDD 開發模式,調整開發的思維,強迫自己寫出可測試性高且耦合性低的程式,讓完成的功能具有測試保護並符合用戶實際需求,這種一氣呵成的感覺真不賴,讓我們一起共勉之。

陳志澤