SpecFlow 如何引入外部資源共用邏輯

有在使用 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

 

如何使用 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