原文:http://confluence.public.thoughtworks.org/display/NMock/Introduction+to+NMock
by Hsue-Shen Tham
NetCobra 翻译
模拟对象(Mocking)
通常在进行单元测试的时候,开发人员常常会碰到这样一些情况:一些功能由于对其它类或系统组件有依赖而难以测试;通常用来解决这种问题的一项技术叫做模拟对象(mock objects)。模拟对象方法允许用模拟的版本来代替被依赖的类(dependent classes),这些模拟对象被传递给要测试的类后,依赖关系就被模拟的版本代替了,而被测试对象则仍然会以为自己所处理的是真实的对象。
以前模拟实例都是人工编写的,不过如今在Java世界和.Net世界都已经有很多工具和框架用于更方便地创建模拟对象;现在已经有很多的资料介绍手工或者使用一些常见Java模拟工具来创建模拟对象的优秀方法(fine art)了,这篇文章要介绍的是使用.Net的一个常见的模拟工具NMock在实现在.Net中的模拟对象测试。
静态 vs. 动态
模拟工具通常可以分为两类:静态的和动态的。对于静态模拟工具,被依赖从属类的模拟对象是作为外部的类生成的,这些类在编译时和被测试对象一起编译,确保类型安全;而动态模拟工具则不添加任何附加的类,通过在运行时动态实现模拟。因为动态模拟工具更容易使用,并且不生成附加的类,因此动态模拟工具比静态模拟工具更常见也更愿意被人们使用;不过相对于静态模拟而言,动态模拟不是类型安全的。
使用 NMock
NMock是一个基于动态代理的模拟工具,用于 C# 开发。NMock使用了代理模式,这允许类实现一个接口并以代理的方式将其它对象的调用转向。NMock生成的模拟是通过在运行时使用动态代理来实现的,这允许模拟对象动态的定义,并不需要添加任何附加的类。
通常,一个模拟的实现基于被依赖的接口而创建;NMock支持对接口和类的模拟,另外它还支持属性模拟。下面是一个使用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 很容易根据一个给定的接口或类来创建一个模拟对象;创建的过程有三步,首先你要实例化一个 Mock 对象,构建时要将你要模拟的接口或类的类型传递给 Mock 对象作为构建参数;然后,你需要记录 Mock 对象的行为并最终通过 Mock 对象的属性来获得一个模拟类型的示例。下面是一个最简单的窗体的模拟对象,该模拟对象基于 IPerson 接口创建,没有记录任何行为。
//通知 NMock 你在模拟哪个接口或类
IMock mockPerson = new DynamicMock(typeof(IPerson));
//获取指定类型的一个模拟实例
IPerson person = (IPerson) mockPerson.MockInstance;
然而如果我们不记录模拟对象应该做些什么或者在它使用之前需要做一些什么处理,那么实际上这个模拟对象是毫无用处的;在下面的例子中,我们将记录并设置 IPerson 的 Name 属性的值。
//通知 NMock 你在模拟哪个接口或类
IMock mockPerson = new DynamicMock(typeof(IPerson));
//设置值
person.ExpectAndReturn("Name", "John Doe");
//获取指定类型的一个模拟实例
IPerson person = (IPerson) mockPerson.MockInstance;
NMock 有一个很长的很有用的 Expect 方法列表,使用这些列表我们可以设置模拟对象的行为,比如方法 A 被调用则返回 B,或者仅当用参数 C 调用方法 A 时才返回 B,或者当 A 方法被调用时则抛出异常 E,甚至我们可以告诉模拟对象它根本就不要指望能够调用方法 A(:-P)。以上的简单例子表明我们希望 Name 属性只应该被调用一次,并且存取该属性时将返回字符串“John Doe”;注意我们希望 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 Doe");
Hello hello = new Hello((IPerson) person.MockInstance);
AssertEquals("Hello John Doe", hello.Greet());
//检验 Name 属性是否只被调用了一次
person.Verify();
}
}