推荐单元测试新的模拟框架:Nsubstitute
目前,.NET已经有很多强大模拟框架,为什么还要再重新写一个呢?按照Nsubstitute的官方说法是:所有的模拟框架都已经有强大的功能,但是现存的框架当中,没有一个满足我们对更简洁语法风格的追求。
第一次看到Nsubstitute,是在看Nunit的源码时发现,实际上Nunit已经不推荐大家使用它原来的Mock框架,它引入了Nsubstitute。经了解,Nsubstitute已经是一个具有两年多历史的模拟框架,也算是比较年轻的框架,它昨天(2012-5-4)刚发布了1.4版本。模拟框架很多,我们的选择很多,但是从现在开始,我们可以考虑一下用Nsubstitute,多了一个选择。
1. Nsubstitute简介
它是一个开源的框架,源码是C#实现的。你可以在这里获得它的源码:https://github.com/nsubstitute/NSubstitute
NSubstitute 更注重替代(Substitute)概念。它的设计目标是提供一个优秀的测试替代的.NET模拟框架。它是一个模拟测试框架,用最简洁的语法,使得我们能够把更多的注意力放在测试工作,减轻我们的测试配置工作,以满足我们的测试需求,帮助完成测试工作。它提供最经常需要使用的测试功能,且易于使用,语句更符合自然语言,可读性更高。对于单元测试的新手或只专注于测试的开发人员,它具有简单、友好的语法,使用更少的lambda表达式来编写完美的测试程序。
NSubstitute 采用的是Arrange-Act-Assert测试模式,你只需要告诉它应该如何工作,然后断言你所期望接收到的请求,就大功告成了。因为你有更重要的代码要编写,而不是去考虑是需要一个Mock还是一个Stub。
对比Moq,NSubstitute 的语法比更简练。这里的主要目的并不是为了比较框架的优劣。
2. 如何获取Nsubstitute?
有两种获得Nsubstitute的方式:
一是通过NuGet或者OpenWrap工具在Vs2010上安装Nsubstitute。
二是通过下载Nsubstitute 组件(https://github.com/nsubstitute/nsubstitute/downloads ),然后在测试项目当中引用 NSubstitute.dll 文件。现在的最新版本是1.4。
3. 测试方法
可以创建接口的实例,并设定接口方法的返回值,供测试。
1) 定义一个最基本的计算器接口:
public interface ICalculator
{
int Add(int a, int b);
int Subtract(int a, int b);
}
2) 创建测试项目、及测试程序
3) 引用命名空间声明
首先,在C#测试源码文件添加命名空间引用
using NSubstitute。
4) 创建接口实例
calculator = Substitute.For<ICalculator>();
5) 设定替代对象的接口方法返回值
指定Add被调用时,传入参数值分别为1、2时,替代的返回值为3
calculator.Add(1, 2).Returns(3);
6) 断言
Assert.AreEqual(calculator.Add(1, 2), 3);
7) Received的使用
另外,我们还可以检验替代对象的指定场景是否被调用,而哪些场景没有被调用,例如:
calculator.Add(1, 2);
calculator.Received().Add(1, 2);
calculator.DidNotReceive().Add(5, 7);
如果场景在之前没有被调用,Received() 断言则会失败。
例如:calculator.Received().Add(2, 3);
我们将会发现测试异常的提示信息如下:
测试方法 Calculator.Tests.ICalculatorTest.AddTest 引发了异常:
NSubstitute.Exceptions.ReceivedCallsException: Expected to receive a call matching:
Add(2, 3)
Actually received no matching calls.
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
Add(*1*, *2*)
NSubstitute支持设置参数返回值,并断言已经被调用。下面是更复杂的使用:
calculator.Add(10, -5);
calculator.Received().Add(10, Arg.Any<int>());//第二个参数的值为任一int类型的整数。
calculator.Received().Add(10, Arg.Is<int>(x => x < 0));//第二个参数的值必须是小于零,否则将报异常
我们还可以使用替代对象的参数匹配及通过Returns()方法得到一些更多的行为:
//定义第一个参数及第二个参数为任意整数,并且返回值为两个参数之和。
calculator.Add(Arg.Any<int>(), Arg.Any<int>()).Returns(x => (int)x[0] + (int)x[1]);
Assert.AreEqual(calculator.Add(5, 10), 15);
4. 测试对象属性及事件委托
1) 定义一个接口:
public interface IPerson
{
string Name { get; set; }
event EventHandler Sleep;
}
2) 创建测试项目、及测试程序
3) 引用NSubstitute.dll,并声明命名空间
首先,在C#测试源码文件添加命名空间引用
using NSubstitute。
4) 创建接口实例
target= Substitute.For<IPerson>();
5) 测试属性的读写操作
target.Name.Returns("小明");
//测试属性的读操作
Assert.AreEqual(target.Name, "小明");
//对属性进行写操作
target.Name = "王二";
Assert.AreEqual(target.Name, "王二");
还可以使用Returns()设置的多个返回值序列
target.Name.Returns("A", "B", "C");
Assert.AreEqual(target.Name, "A");
Assert.AreEqual(target.Name, "B");
Assert.AreEqual(target.Name, "C");
6) 测试事件
bool eventWasRaised = false;
//定义委托事件
target.Sleep += (sender, args) => eventWasRaised = true;
//触发事件
target.Sleep += Raise.Event();
//断言事件被触发
Assert.IsTrue(eventWasRaised);