系列目錄html
Nunit裏提供了豐富的數據測試功能,雖然Xunit裏提供的比較少,可是也能知足不少場景下使用了,若是數據場景很是複雜,Nunit和Xunit都是沒法勝任的,有很多測試者選擇本身編寫一個數據提供程序,可是更建議使用AutoFixture框架,一是由於本身工做中寫的每每只是爲了解決某個或者部分問題,只能隨着業務邏輯的擴展才能不斷的健壯起來,二是這樣的框架每每缺乏良好文檔,主要由核心開發者口口相傳,這就致使後來者遇到不明白了功能就去問核心開發者,影響這些開發者的其它工做.數據庫
下面介紹一下Xunit裏的數據提供方式.框架
InlineData至關於Nunit裏的TestCase,用註解的方式給測試方法提供數據.
咱們經過如下代碼片斷了解它的基本用法ide
[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顧名思義,就是成員數據,它相似於Nunit裏的TestCaseSource
可是不一樣的是Xunit的MemberData的數據提供者必須是當前測試類的成員,測試數據提供者和測試方法耦合在一塊可能不是太好的設計,若是須要大量測試數據,建議使用AutoFixture.設計
經過屬性提供測試數據適應於一些比較簡單的場景,這些數據是簡單的,肯定的.
下面看一個示例code
[Theory] [MemberData(nameof(UnitTest1.ProvideData))] public void Test1(int x,int y) { int result = x + y; Assert.Equal(x + y, result); } public static IEnumerable<object[]> ProvideData { get { yield return new object[] { 3, 4 }; yield return new object[] { 5, 9 }; yield return new object[] { 11, 13 }; } }
以上代碼中,測試方法和數據提供者必須位於同一個類中,而且數據提供者必須是一個公開的,靜態的屬性.而且它的集合元素類型必須是Object類型.像以上Test1方法雖然須要的是int類型參數,可是提供者類型也必須是object類型,而不能是具體類型.htm
以上數據提供屬性一共yield了三組數據,所以測試方法會生成三個測試實例.對象
[Theory] [MemberData(nameof(UnitTest1.ProvideData))] public void Test1(int x,int y) { int result = x + y; Assert.Equal(x + y, result); } public static IEnumerable<object[]> ProvideData() { yield return new object[]{3,4 }; yield return new object[] {5, 9}; yield return new object[] { 11, 13 }; }
你可能會感受以上方法和屬性並沒太大的區別,其實方法的功能更爲強大,由於屬性沒法動態指定參數,而方法能夠,咱們能夠指定方法接收動態運行時須要的參數,而後在MemberData的構造函數裏傳入參數來動態獲取數據.
成員提供數據能夠把外部對象做爲本類成員,而後給測試方法提供數據.外部對象須繼承自TheoryData.
咱們定義一個MyDataprovider
public class MyDataprovider<TData1,TData2>:TheoryData<TData1,TData2> { public MyDataprovider(IEnumerable<TData1> dataSource1,IEnumerable<TData2> 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); } } } }
咱們再看測試類
public class UnitTest1 { public static MyDataprovider<int, int> myprovider = new MyDataprovider<int, int>(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改成以下就能夠了
public class MyDataprovider<TData1,TData2>:TheoryData<TData1,TData2> { public MyDataprovider(IEnumerable<TData1> dataSource1,IEnumerable<TData2> 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<Object[]>泛型接口,Xunit會自動的調用其GetEnumerator方法來遍歷數據而後提供給測試類.
咱們看如下數據提供類
public class MyDataClassProvider:IEnumerable<object[]> { public IEnumerator<object[]> GetEnumerator() { yield return new object[] {3, 4}; yield return new object[] {5, 9}; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }
以上類型的GetEnumerator繼承自接口,咱們這裏只提供了一些簡單數據,固然帶能夠編寫更爲複雜的數據提供邏輯,好比從數據庫裏遍歷,而後轉化爲可遍歷集合.
下面再看看它是如何被使用的.
[Theory] [ClassData(typeof(MyDataClassProvider))] public void Test1(int x,int y) { var result = x + y; Assert.Equal(x + y, result); }
這裏使用ClassData註解,傳入一個type類型.運行的時候Xunit即可以給測試方法提供測試數據了