1. 程式人生 > >.netcore持續整合測試篇之Xunit資料驅動測試

.netcore持續整合測試篇之Xunit資料驅動測試

>[系列目錄](https://www.cnblogs.com/tylerzhou/p/11204826.html) Nunit裡提供了豐富的資料測試功能,雖然Xunit裡提供的比較少,但是也能滿足很多場景下使用了,如果資料場景非常複雜,Nunit和Xunit都是無法勝任的,有不少測試者選擇自己編寫一個數據提供程式,但是更建議使用AutoFixture框架,一是因為自己工作中寫的往往只是為了解決某個或者部分問題,只能隨著業務邏輯的擴展才能不斷的健壯起來,二是這樣的框架往往缺少良好文件,主要由核心開發者口口相傳,這就導致後來者遇到不明白了功能就去問核心開發者,影響這些開發者的其它工作. 下面介紹一下Xunit裡的資料提供方式. ### InlineData InlineData相當於Nunit裡的TestCase,用註解的方式給測試方法提供資料. 我們通過以下程式碼片段瞭解它的基本用法 ```cs [Theory] [InlineData(1, 2)] [InlineData(5, 9)] public void Test1(int x,int y) { int result = x + y; Assert.Equal(x + y, result); } ``` 以上方法與普通測試方法相比最大的區別是它使用的是Theory註解,而不是fact註解.使用Theory註解的方法必須提供相應的引數,否則會報編譯錯誤. 以上測試我們提供了兩組InlineData,這樣在測試執行的時候測試方法就會根據這些資料生成兩個方法例項.同Nunit裡的表現行為相似. ## MemberData MemberData顧名思義,就是成員資料,它類似於Nunit裡的`TestCaseSource`但是不同的是Xunit的MemberData的資料提供者必須是當前測試類的成員,測試資料提供者和測試方法耦合在一塊可能不是太好的設計,如果需要大量測試資料,建議使用AutoFixture. ### 資料提供者之屬性提供資料 通過屬性提供測試資料適應於一些比較簡單的場景,這些資料是簡單的,確定的. 下面看一個示例 ```cs [Theory] [MemberData(nameof(UnitTest1.ProvideData))] public void Test1(int x,int y) { int result = x + y; Assert.Equal(x + y, result); } public static IEnumerable ProvideData { get { yield return new object[] { 3, 4 }; yield return new object[] { 5, 9 }; yield return new object[] { 11, 13 }; } } ``` 以上程式碼中,測試方法和資料提供者必須位於同一個類中,並且資料提供者必須是一個公開的,靜態的屬性.並且它的集合元素型別必須是Object型別.像以上Test1方法雖然需要的是int型別引數,但是提供者型別也必須是object型別,而不能是具體型別. 以上資料提供屬性一共yield了三組資料,因此測試方法會生成三個測試例項. ### 資料提供者之方法提供資料 ```cs [Theory] [MemberData(nameof(UnitTest1.ProvideData))] public void Test1(int x,int y) { int result = x + y; Assert.Equal(x + y, result); } public static IEnumerable ProvideData() { yield return new object[]{3,4 }; yield return new object[] {5, 9}; yield return new object[] { 11, 13 }; } ``` 你可能會感覺以上方法和屬性並沒太大的區別,其實方法的功能更為強大,因為屬性無法動態指定引數,而方法可以,我們可以指定方法接收動態執行時需要的引數,然後在MemberData的建構函式裡傳入引數來動態獲取資料. ### 資料提供者之成員提供資料 成員提供資料可以把外部物件作為本類成員,然後給測試方法提供資料.外部物件須繼承自TheoryData. 我們定義一個MyDataprovider ```cs public class MyDataprovider:TheoryData { public MyDataprovider(IEnumerable dataSource1,IEnumerable datasource2) { if (dataSource1 == null || datasource2 == null || !dataSource1.Any() || !datasource2.Any()) throw new Exception("集合不為能空或者null"); foreach (TData1 data1 in dataSource1) { foreach (TData2 data2 in datasource2) { Add(data1, data2); } } } } ``` 我們再看測試類 ```cs public class UnitTest1 { public static MyDataprovider myprovider = new MyDataprovider(new[] {3, 4, 5}, new[] {6, 7, 8}); [Theory] [MemberData(nameof(UnitTest1.myprovider))] public void Test1(int x,int y) { int result = x + y; Assert.Equal(x + y, result); } } ``` 我們在new MyDataprovider的時候通過建構函式傳入兩個集合,MyDataprovider繼承了TheoryData的Add方法,把資料新增到theorydata中. 以上方法實際上生成了一個笛卡爾集{{3,6},{3,7},{3,8},{4,6},{4,7},{4,8},{5,6},{5,7},{5,8}}類似於Nunit裡的values註解不加sequential,這個行為很多時候可能並不是我們想要的,我們想要的可能是{{3,6},{4,7},{5,8}}這樣的組合,這其實是可以在MyDataprovider裡自定義的. 我們把MyDataprovider改為如下就可以了 ```cs public class MyDataprovider:TheoryData { public MyDataprovider(IEnumerable dataSource1,IEnumerable datasource2) { if (dataSource1 == null || datasource2 == null || !dataSource1.Any() || !datasource2.Any()) throw new Exception("集合不為能空或者null"); var count1 = dataSource1.Count(); var count2 = datasource2.Count(); if (count1 != count2) throw new ArgumentException("兩個集合長度必須相等"); for (int i = 0; i < count1; i++) { Add(dataSource1.ElementAt(i), datasource2.ElementAt(i)); } } } ``` 這樣雖然可以把資料提供者轉移到外部了,然而去把簡單的問題搞的相當複雜! ### 資料提供者之類資料提供者 前面介紹的資料提供者除了InlineData比較常用外,其它幾個都不是很實用,因為資料和測試方法混合在一個類中,違反了職責單一的原則,最後一個看似比較好的解開了耦合,實際上卻帶來了更高的複雜度.這裡介紹ClassDataAttribute,類資料提供者. 類資料提供者需要實現IEnumerable泛型介面,Xunit會自動的呼叫其GetEnumerator方法來遍歷資料然後提供給測試類. 我們看以下資料提供類 ```cs public class MyDataClassProvider:IEnumerable { public IEnumerator GetEnumerator() { yield return new object[] {3, 4}; yield return new object[] {5, 9}; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } ``` 以上型別的GetEnumerator繼承自介面,我們這裡只提供了一些簡單資料,當然帶可以編寫更為複雜的資料提供邏輯,比如從資料庫裡遍歷,然後轉化為可遍歷集合. 下面再看看它是如何被使用的. ```cs [Theory] [ClassData(typeof(MyDataClassProvider))] public void Test1(int x,int y) { var result = x + y; Assert.Equal(x + y, result); } ``` 這裡使用ClassData註解,傳入一個type型別.執行的時候Xunit便可以給測試方法提供測試