.netcore持续集成测试篇之Xunit数据驱动测试

系列目录html

Nunit里提供了丰富的数据测试功能,虽然Xunit里提供的比较少,可是也能知足不少场景下使用了,若是数据场景很是复杂,Nunit和Xunit都是没法胜任的,有很多测试者选择本身编写一个数据提供程序,可是更建议使用AutoFixture框架,一是由于本身工做中写的每每只是为了解决某个或者部分问题,只能随着业务逻辑的扩展才能不断的健壮起来,二是这样的框架每每缺乏良好文档,主要由核心开发者口口相传,这就致使后来者遇到不明白了功能就去问核心开发者,影响这些开发者的其它工做.数据库

下面介绍一下Xunit里的数据提供方式.框架

InlineData

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

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即可以给测试方法提供测试数据了