有在使用 SpecFlow 也許會遇到許多動作重覆的問題,雖然產生Step檔案可以幫你產生不重複的陳述句,但如果重覆的邏輯是屬於跨 Feature 的範圍,或是你把 SpecFlow 的測試又跨分至其他 project,那該怎麼共用邏輯?
對於 specflow 不太熟悉可以先看這篇
SpecFlow 的強項就是允許你用有語意的方式來陳述你的測試,尤其方面給團隊裡不懂程式邏輯的成員看測試報告。
假若你的SpecFlow 裡,每次都需要針對中文語句進行結果的轉換,如下例是判斷奇偶數的測試
Feature: MathFeature
Simple mod calculator for math calculation
Scenario Outline: 判斷奇數偶數
Given 輸入數 <輸入數>
When 經過計算後
Then 結果應該會是 <預期的結果>
Examples:
| 輸入數 | 預期的結果 |
| 10 | "偶數" |
| 1 | "奇數" |
[Binding]
[Scope(Feature = "MathFeature")]
public class SpecFlowFeature1Steps
{
private static FeatureContext _featureContext;
private readonly ScenarioContext _scenarioContext;
public SpecFlowFeature1Steps(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[BeforeFeature]
public static void BeforeFeature(FeatureContext featureContext)
{
_featureContext = featureContext;
featureContext["ResultMap"] = new Dictionary<string, int>
{
["偶數"] = 0,
["奇數"] = 1
};
}
[Given(@"輸入數 (.*)")]
public void Given輸入數(int inputNum)
{
_scenarioContext["InputNum"] = inputNum;
}
[When(@"經過計算後")]
public void When經過計算後()
{
var inputNum = _scenarioContext.Get<int>("InputNum");
var result = ClsMath.Mod(inputNum, 2);
_scenarioContext["Result"] = result;
}
[Then(@"結果應該會是 ""(.*)""")]
public void Then結果應該會是(string expceted)
{
var resultMap = _featureContext.Get<Dictionary<string, int>>("ResultMap");
var expcetedResult = resultMap[expceted];
var actualResult = _scenarioContext.Get<int>("Result");
actualResult.Should().Be(expcetedResult);
}
假若我們對於奇數偶數的結果轉換會有重複性的需求,那我該怎麼共用這部份的邏輯
有幾個方法可以達到
1. 寫個 Helper 進行轉換 (這方法雖然簡單,但不是我們今天要使用的方法)
2. 在 public void Then結果應該會是(string expceted) 這裡的判斷邏輯上進行轉換,但若想想你有許多 feature 裡,都有這樣的轉換邏輯,你該怎麼辦?
3. 使用 SepcFlow Hook
Step 1. 建立共用專案 SharedSpecFlow
Step 2. 在 SharedSpecFlow 建立SharedHook.cs
裡面要做的事非常簡單,就是將你希望共用的部份移入即可
[Binding]
public sealed class SharedHooks
{
[BeforeFeature]
public static void BeforeFeature(FeatureContext featureContext)
{
featureContext["ResultMap"] = new Dictionary<string, int>
{
["偶數"] = 0,
["奇數"] = 1
};
}
}
Step 3. 將原本專案要共用的部份移除,但仍需保留 BeforeFeature 以便注入 _featureContext 的物件
[BeforeFeature]
public static void BeforeFeature(FeatureContext featureContext)
{
_featureContext = featureContext;
//移至 SharedSpecFlow
//featureContext["ResultMap"] = new Dictionary<string, int>
//{
// ["偶數"] = 0,
// ["奇數"] = 1
//};
}
Step 4. 在 專案的 reference 裡,請引用 SharedSpecFlow 專案
Step 5. 新增 specflow.json 引入外部元件 SharedSpecFlow (這部份你也可以使用 app.config 的方式引入)
{
"stepAssemblies": [
{
"assembly": "SharedSpecFlow"
}
]
}
再重新測試一次即會發現修改範圍不算太大,且都可以正常執行成功
結論
優點: 方便我們將共用的部份移至共用專案,以便減少重複的程式碼
缺點: 接手維護的人若不熟悉 spec flow 較難發現這部份的程式碼是透過 hook 的方式共用的,在 debug 由於是跨專案的方式,所以不易 debug 至外部共用的專案,需使用其他方式 debug (故共用的部份又要跨專案的話,請記得不要將邏輯寫的太複雜造成後續的問題)
參考文件
https://blog.yowko.com/specflow/
https://dotblogs.com.tw/hatelove/2013/12/11/specflow-bind-with-scope
https://docs.specflow.org/projects/specflow/en/latest/Bindings/Use-Bindings-from-External-Assemblies.html