.net持续集成测试篇之Nunit参数化测试

系列目录html

在进行单元测试的时候,不少时候,不少时候咱们都是在单元测试方法内部提供特定的值,可是这样测试每每形成样本数不足从而致使覆盖的结果不够全面,不少时候咱们更想提供来自外部的,知足条件的一组值来进行测试.其实Nunit框架自己提供了为测试用例提供值的能力.咱们能够对它进行扩展来实现导入外部的值来填充到测试方法内部.不少朋友也本身写了很多按照必定规则生成值的方法.可是每每都是在方法内部直接调用,这样就会和单元测试的逻辑混杂在一块,致使测试方法自己不够简洁.其实能够根本测试框架自己的能力改形成为注解的方式,这样参数生成逻辑和测试逻辑一目了然.后面咱们还会讲解基于Autofixture框架来生成填充数据,autofixture相比咱们本身写的值填充方法,每每功能更增强大.后面咱们将见证其强大之处.框架

提供普通参数

很容易发现,单元测试的方法都是不带参数的,有些时候咱们须要为一个要测试的方法(并不是单元测试方法)提供多个参数进行测试,这就会致使一个问题:咱们须要写不少相似的测试方法,只是参数不同,这样维护起来不方便,同时大量重复的工做也很烦.下面介绍Nunit里如何为测试提供参数dom

int  Add(int x, int y)
        {
            return x + y;
        }

以上是咱们要测试的方法.ide

虽然Nunit测试方法正常状况下是不支持参数的,可是若是对参数添加的values注解,Nunit便会把这些参数应用到测试.
咱们看一下编写的测试方法单元测试

[Test]
        public void DemoTest([Values(3,4,5)]int a,[Values(6,7,8)]int b)
        {
            var result = Add(a, b);
            Assert.AreEqual(a + b, result);
        }

咱们运行以上方法,能够看到测试结果经过,可是咱们看一下测试面板(Test Explorer)测试

avatar
经过截图咱们很容易发现,这个测试方法一共运行的九次!再仔细看看方法对应的参数,能够看到它是使用组合的方式把全部的可能都组合一遍.code

可是有些时候咱们想要的不是这样的组合,咱们想要的更多时候是(3,6),(4,7),(5,8)这样的组合,如何作到呢,仍然看一段示例代码htm

[Test]
        [Sequential]
        public void DemoTest([Values(3,4,5)]int a,[Values(6,7,8)]int b)
        {
            var result = Add(a, b);
            Assert.AreEqual(a + b, result);
        }

咱们看看运行结果blog

avatar

此次只运行了三次,而且参数的组合正如咱们期待的.
这个方法和上面的同样,只是多了一个[Sequential]注解字符串

注意Values注解里的参数都是Object类型,运行时候转换为参数的真正类型,若是没法转换则会抛出异常.好比[Values("a")]int x因为a是字符串类型,经过内置方法没法转换为int,因些会抛出异常.

提供基于范围的参数

上面的测试Values(3,4,5)和Values(6,7,8)都是连续的数字,若是链接的参数更多,咱们可使用基于范围的参数.

看如下示例代码

[Test]
        [Sequential]
        public void DemoTest([Range(3,5)]int a,[Range(6,8)]int b)
        {
            var result = Add(a, b);
            Assert.AreEqual(a + b, result);
        }

咱们把Values注解改成Range注解,就ok了

提供随机参数

咱们还能够为测试提供一些随机数,以使测试变得更随机,覆盖范围更大

这里要使用Random注解
请看下面示例

[Test]
       [Sequential]
        public void DemoTest([Random(3)]int a, [Random(3)]int b)
        {
            var result = Add(a, b);
            Assert.AreEqual(a + b, result);
        }

Random的参数为要生成随机数的个数.

Random还有一重载以支持生成随机数的最大值和最小值

[Test]
       [Sequential]
        public void DemoTest([Random(3,10,2)]int a, [Random(5,9,3)]int b)
        {
            var result = Add(a, b);
            Assert.AreEqual(a + b, result);
        }

示例中Random的三个参数分别是最小值,最大值和个数

[info]Random的最大值和最小值不只能够是整数,也能够是小数

提供计算参数

先看一个示例

[Test]
       [Sequential]
        public void DemoTest(DateTime dt1)
       {
           DateTime dt2 = default(DateTime);
           Assert.Greater(dt1, dt2);
       }

这里测试方法的参数是Datetime类型,咱们如何给给它提供值呢,不少人可能会想使用Values[DateTime.Now] 来注解dt1参数,然而不幸的是Values注解只接受const类型的值,这里介绍ValueSource注解来解决这个问题.

ValueSource的机制是使用一个方法来获取值,而后提供给测试方法参数,它接受一个字符串类型的参数,用于指定提供值的方法名.

咱们用如下方法生成一些DateTime值

static IEnumerable<DateTime> GetPeople()
        {
            yield return DateTime.Now;
            yield return DateTime.Now.AddDays(2);
        }

以上方法生成了一个包含两个DateTime值的集合.下面咱们看如何使用它

[Test]
        public void DemoTest([ValueSource(nameof(FirstUnitTest.GetPeople))]DateTime dt1)
       {
           DateTime dt2 = default(DateTime);
           Assert.Greater(dt1, dt2);
       }

咱们使用nameof获取刚才生成的用于提供值的方法,做为ValueSource的参数.

使用nameof而不是使用手写字符串的好处在于nameof能够有智能提示,防止手写出现错误,另外就是若是方法名更改,这里将会抛出了一个错误,静态字符串不会提示错误,若是在运行时找不到这个方法则会抛出运行时错误

用于为ValueSource提供值的方法必须是静态的

以上代码,咱们把提供值的方法直接写在测试类里,这并非一种很好的实践,一种好的作法是把全部的用于提供值的方法放在一个外部的类中.

咱们把这个类移动到一个叫做MyValueProvider的类中
代码以下

public class MyValueProvider
    {
        public static IEnumerable<DateTime> GetPeople()
        {
            yield return DateTime.Now;
            yield return DateTime.Now.AddDays(2);
        }
    }

单元测试方法改为以下:

[Test]
        public void DemoTest([ValueSource(typeof(MyValueProvider),nameof(MyValueProvider.GetPeople))]DateTime dt1)
       {
           DateTime dt2 = default(DateTime);
           Assert.Greater(dt1, dt2);
       }

若是把值提供方法不在本类中(当前测试方法所在的类),提供一个Type类型(提供值的方法所在的类的类型)做为第一个参数,方法名做为第二个参数.

上面讲的都是基于参数注解的值提供方法,这里基于方法的注解的值提供方法.固然,它完成的功能基于参数注解的方法也一样能完成.

TestCaseAttribute注解

看如下代码片断

[TestCase(3,4)]
        public void DemoTest(int x,int y)
       {
           var val = Add(x, y);
           Assert.AreEqual(x + y, val);
       }

其中用到的Add方法代码以下

int  Add(int x, int y)
        {
            return x + y;
        }

TestCase的工做原理是这样的,它提供的值是基于位置的,每个位置处的值赋值给第一个参数,第二个位置处的值提供给第二个参数...

有了TestCase注解以后,Test注解再也不是必要的.

TestCaseSourceAttribute注解

从上ValueSource咱们很容易想到可能会有TestCaseSource,实际上也确实是这样的,TestCaseSource功能也同ValueSource同样,用于提供基于计算的结果.

用于提供值的类以下

public class MyValueProvider
   {
       public static ArrayList ar = new ArrayList
       {
           new int[] {3, 4},
           new int[] {5, 9},
           new int[] {9, 22}
       };
   }

测试方法以下

[TestCaseSource(typeof(MyValueProvider),nameof(MyValueProvider.ar))]     
        public void DemoTest(int x,int y)
       {
           var val = Add(x, y);
           Assert.AreEqual(x + y, val);
       }

从这个例子咱们可看到,不只方法能够提供值,属性,普通字段也能够提供值

为TestCaseSource提供值的字段,方法,属性也必须是静态的

TestCase和TestCaseSource都支持多重注解,有几个注解,测试方法就会运行几回.