天天看點

.netcore持續內建測試篇之Xunit資料驅動測試一

系列目錄

Nunit裡提供了豐富的資料測試功能,雖然Xunit裡提供的比較少,但是也能滿足很多場景下使用了,如果資料場景非常複雜,Nunit和Xunit都是無法勝任的,有不少測試者選擇自己編寫一個資料提供程式,但是更建議使用AutoFixture架構,一是因為自己工作中寫的往往隻是為了解決某個或者部分問題,隻能随着業務邏輯的擴充才能不斷的健壯起來,二是這樣的架構往往缺少良好文檔,主要由核心開發者口口相傳,這就導緻後來者遇到不明白了功能就去問核心開發者,影響這些開發者的其它工作.

下面介紹一下Xunit裡的資料提供方式.

InlineData

InlineData相當于Nunit裡的TestCase,用注解的方式給測試方法提供資料.

我們通過以下代碼片段了解它的基本用法

[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.

資料提供者之屬性提供資料

通過屬性提供測試資料适應于一些比較簡單的場景,這些資料是簡單的,确定的.

下面看一個示例

[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類型,而不能是具體類型.

以上資料提供屬性一共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便可以給測試方法提供測試資料了

下一篇: git知識