系列目录
在进行单元测试的时候, 很多时候, 很多时候我们都是在单元测试方法内部提供特定的值, 但是这样测试往往造成样本数不足从而导致覆盖的结果不够全面, 很多时候我们更想提供来自外部的, 满足条件的一组值来进行测试. 其实 Nunit 框架本身提供了为测试用例提供值的能力. 我们可以对它进行扩展来实现导入外部的值来填充到测试方法内部. 很多朋友也自己写了不少按照一定规则生成值的方法. 但是往往都是在方法内部直接调用, 这样就会和单元测试的逻辑混杂在一块, 导致测试方法本身不够简洁. 其实可以根本测试框架本身的能力改造成为注解的方式, 这样参数生成逻辑和测试逻辑一目了然. 后面我们还会讲解基于 Autofixture 框架来生成填充数据, autofixture 相比我们自己写的值填充方法, 往往功能更加强大. 后面我们将见证其强大之处.
提供普通参数
很容易发现, 单元测试的方法都是不带参数的, 有些时候我们需要为一个要测试的方法 (并非单元测试方法) 提供多个参数进行测试, 这就会导致一个问题: 我们需要写很多类似的测试方法, 只是参数不一样, 这样维护起来不方便, 同时大量重复的工作也很烦. 下面介绍 Nunit 里如何为测试提供参数
- int Add(int x, int y)
- {
- return x + y;
- }
以上是我们要测试的方法.
虽然 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)
通过截图我们很容易发现, 这个测试方法一共运行的九次! 再仔细看看方法对应的参数, 可以看到它是使用组合的方式把所有的可能都组合一遍.
但是有些时候我们想要的不是这样的组合, 我们想要的更多时候是 (3,6),(4,7),(5,8) 这样的组合, 如何做到呢, 仍然看一段示例代码
- [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);
- }
我们看看运行结果
这次只运行了三次, 并且参数的组合正如我们期待的.
这个方法和上面的一样, 只是多了一个 [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 都支持多重注解, 有几个注解, 测试方法就会运行几次.
来源: https://www.cnblogs.com/tylerzhou/p/11312957.html