.Net单元测试之NMock
NMock是一款.NET平台下的基于动态代理的Mock模拟对象类库,用于C#开发。Mock对象使测试更简单,它测试单个组件或单个类时不需要其它所有组件真正实现。也就是说我们可以仅仅只是测试一个类,NMock生成的模拟是通过在运行时使用动态代理来实现的,这允许模拟对象动态的定义,并不需要添加任何附加的类。 通常,一个模拟的实现基于被依赖的接口而创建;NMock支持对接口和类的模拟,另外它还支持属性模拟。比起测试一个完整的对象关系树更容易查清楚Bug。Mock对象一般用于以测试为驱动的开发当中。
- 为什么需要Mock
单元测试的基本原则应该是每次只验证一个方法,但是倘若遇到这样一种情况:
测试方法依赖于其他一些难以操控的东西,如:网络,数据库。或者是你测试的代码依赖于系统的其他部分,甚至是系统的多个其他部分。在这种情况下,倘若不小心,最终可能发现自己不小心几乎初始化了系统的每一个组件,而所有这一切只是为了给某一个测试创造必要的运行环境。这不仅花费了大量的时间,要命的是这样的测试用例会被引入大量的耦合因素,很难到达“单元”测试的目的。我们该怎么办呢? 这是Mock的测试方法就派上用场了。Mock的英文的字面意思是:嘲笑,模仿,欺骗的意思。通过Mock,我们可以创建很多真实对象的替代品,在测试用例中使用它。
- 什么情况下考虑使用Mock
1) 真实的对象具有不可确定的行为(如:程序需要通过web service获得股票的实时价格)
2) 对象很难被创建(如系统环境很难初始化)
3) 真实对象的某些行为很难被触发(如网络错误,数据库ID自增序列溢出)
4) 真实的对象令程序运行很缓慢
5) 真实对象含有UI等不方便测试的因素
6) 测试需要询问真实对象是如何被调用的(如异步调用的情况,需要验证Callbak的函数)
7) 真实的对象目前还不存在(如依赖于其他项目组或则需要新的硬件系统)
- 进行Mock测试的步骤
- 测试代码首先引用NMock的框架
1) 定义一个接口来描述这个对象
2) 产品代码实现这个接口
3) 测试中Mock对象实现这个接口
下面是一个使用NMock的快速教程:
示例 现在我们来做一个简单的“Hello”例子,测试 Hello 类的 Greet() 方法,Hello 类依赖于一个 Person 对象,并将根据 Person 的名字向对应的账号发送祝贺(Greet)。这个例子很简单,其实用不到模拟对象,不过用来理解 NMock 是很合适的。
public interface IPerson { string Name { get; } }
然后定义具有 Greet 方法的 Hello 类,可以根据 IPerson 的名字发送祝贺信息。
public class Hello { IPerson person; public Hello(IPerson person) { this.person = person; } public String Greet() { return "Hello " + person.Name; } }
我们可以看到,Hello 类对 IPerson 接口有依赖。
在我们继续对 Hello 类的开发之前,让我们来学习一些关于 NMock 的基础知识。使用 NMock 很容易根据一个给定的接口或类来创建一个模拟对象;
1)首先你要实例化一个 Mock 对象,构建时要将你要模拟的接口或类的类型传递给 Mock 对象作为构建参数;
2)然后,你需要记录 Mock 对象的行为并最终通过 Mock 对象的属性来获得一个模拟类型的示例。下面是一个最简单的窗体的模拟对象,该模拟对象基于 IPerson 接口创建,没有记录任何行为。
//通知 NMock 你在模拟哪个接口或类 IMock mockPerson = new DynamicMock(typeof(IPerson)); //获取指定类型的一个模拟实例 IPerson person = (IPerson) mockPerson.MockInstance;
然而如果我们不记录模拟对象应该做些什么或者在它使用之前需要做一些什么处理,那么实际上这个模拟对象是毫无用处的;在下面的例子中,我们将记录并设置 IPerson 的 Name 属性的值。
//通知 NMock 你在模拟哪个接口或类 IMock mockPerson = new DynamicMock(typeof(IPerson));
//设置值 mockPerson.ExpectAndReturn("Name", "John"); //获取指定类型的一个模拟实例 IPerson person = (IPerson) mockPerson.MockInstance;
NMock 有一个很长的很有用的 Expect 方法列表,使用这些列表我们可以设置模拟对象的行为,比如方法 A 被调用则返回 B,或者仅当用参数 C 调用方法 A 时才返回 B,或者当 A 方法被调用时则抛出异常 E,甚至我们可以告诉模拟对象它根本就不要指望能够调用方法 A。以上的简单例子表明我们希望 Name 属性只应该被调用一次,并且存取该属性时将返回字符串“John”;注意我们希望 Name 属性只应该被调用一次,我们可以通过调用模拟对象的 Verify() 方法来验证这一点。
//在模拟对象上的预期检验 person.Verify();
这是一个简略的预期设置的方法列表:
Expect(string methodName, object[] args)
ExpectAndReturn(string methodName, object returnVal, object[] args)
ExpectAndThrow(string methodName, Exception exceptionVal, object[] args)
ExpectNoCall(string methodName)
现在设置好所有的基本条件,我们就可以简单的在对 Hello 类的测试中应用 NMock 了。
这就是我们的测试类:
[TestFixture] public class HelloTest : Assertion { [Test] public void TestExpect() { //模拟依赖 IMock person = new DynamicMock(typeof(IPerson)); //设置值 person.ExpectAndReturn("Name", "John"); Hello hello = new Hello((IPerson) person.MockInstance); AssertEquals("Hello John", hello.Greet()); //检验 Name 属性是否只被调用了一次 person.Verify(); } }
.Net单元测试之NMock