[SpecFlow]在測試程式中比較單一物件與物件集合
前言
	前一篇文章:[Unit Testing]如何驗證兩個自訂型別物件集合相等 提到了如何比較自訂的物件,以及自訂物件的集合是否相等。(偷吃步可以透過 LINQ 的 SequenceEqual() 搭配匿名型別與 Tuple 來使用)
這篇文章,則是使用 SpecFlow 的 Assist Helper 直接從 Scenario 取得的測試案例資料進行比較。
範例
這篇文章簡單的舉了一個例子來說明,怎麼驗證自訂型別的單一物件以及集合。 Feature 檔如下:
Feature: 射手預備
Scenario: 第一位射手是誰
	Given 射手群清單為
	| Name | Id |
	| Joey | 1  |
	| Bill | 2  |
	| Demo | 3  |
	When I 取得第一位射手
	#錯誤的測試案例,預期第一筆應該為Joey才對
	Then 第一位射手應為 
	| Name | Id |
	| Bill | 2  |
	
Scenario: 下一批射手預備
	Given 射手群清單為
	| Name | Id |
	| Joey | 1  |
	| Bill | 2  |
	| Demo | 3  |
	When I 呼叫下一波射手預備,一次 2 位
	#錯誤的測試案例,預期第一筆應該為{Joey,1} 第二筆應為 {Bill,2}才對
	Then 射手清單為
	| Name | Id |
	| Joey | 2  |
	| Bill | 3  |
兩個測試案例可以看到刻意放了錯誤的 Then ,這裡是為了讓讀者們看到,當驗證失敗時,錯誤訊息的呈現方式。
接著來看重點, Steps 該怎麼設計。 Steps 程式碼如下:
using System.Collections.Generic;
using System.Linq;
using TechTalk.SpecFlow;
using TechTalk.SpecFlow.Assist;
namespace SpecflowAssertSample
{
    [Binding]
    [Scope(Feature = "射手預備")]
    public class 射手預備Steps
    {
        private Target target;
        [BeforeScenario]
        public void BeforeScenario()
        {
            target = null;
        }
        [Given(@"射手群清單為")]
        public void Given射手群清單為(Table table)
        {
            var shooters = table.CreateSet<Person>().ToList();
            this.target = new Target(shooters);
        }
        [When(@"I 呼叫下一波射手預備,一次 (.*) 位")]
        public void WhenI呼叫下一波射手預備一次N位(int count)
        {
            List<Person> actual = target.GetNext(count);
            ScenarioContext.Current.Set<List<Person>>(actual);
        }
        [Then(@"射手清單為")]
        public void Then射手清單為(Table table)
        {
            //var expected = table.CreateSet<Person>().ToList();
            var actual = ScenarioContext.Current.Get<List<Person>>();
            table.CompareToSet<Person>(actual);
        }
        [When(@"I 取得第一位射手")]
        public void WhenI取得第一位射手()
        {
            Person actual = target.GetFirstShooter();
            ScenarioContext.Current.Set<Person>(actual, "first");
        }
        [Then(@"第一位射手應為")]
        public void Then第一位射手應為(Table table)
        {
            var actual = ScenarioContext.Current.Get<Person>("first");
            //var expected = table.CreateInstance<Person>();
            table.CompareToInstance<Person>(actual);
        }
    }
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    public class Target
    {
        private List<Person> shooters;
        public Target(List<Person> shooters)
        {
            this.shooters = shooters;
        }
        internal List<Person> GetNext(int count)
        {
            return this.shooters.Take(count).ToList();
        }
        internal Person GetFirstShooter()
        {
            return this.shooters.First();
        }
    }
}
重點說明:
- 要使用 SpecFlow 的 Assist Helper,記得引用 TechTalk.SpecFlow.Assist 的命名空間
 - 
		
table.CreateSet<T>與table.CreateInstance<T>的使用方式,請參考:[BDD][Tool][SpecFlow]Scenario 上使用 table 來呈現集合 - 
		重點在 Then 的部分,當要比較單一物件時,可以直接使用 
table.CompareToInstance<T>(instance)即可。 - 
		當要比較物件集合時,可以直接使用 
table.CompareToSet<T>(set)即可。 
驗證失敗的訊息
第一個測試案例,因為是 Scenario 就寫錯了,所以 Expected 是 Scenario 上的。
第二個測試案例錯誤訊息如下:
可以看到驗證失敗訊息的呈現,要比自己用 MSTest 等 Assert 來得清楚許多, SpecFlow 會把 property 的名稱與值都顯示出來。
結論
有用 SpecFlow 的 table 來呈現物件時,除了需求與測試案例說明更加清楚以外,不管是單一物件或集合,在 Steps 中要進行取值或驗證,都是一塊小蛋糕。比起自己寫或其他 Test Framework 內建的 Assert ,要友善跟好用許多,相信這個小技巧,一定可以讓大家寫測試程式更加輕鬆簡便!
Reference
blog 與課程更新內容,請前往新站位置:http://tdd.best/
