第七节:使用Mock对象
7.1简单的替换
通常那些大腕明星都有自己的替身,那么对于单元测试来说有时候我们也需要自己的替身,比如我们并不像在真正的数据库上做测试。
假设我们在代码中调用Now属性来返回系统当前的日期和时间

public DateTime Now
{
get {
return DateTime.Now;
}
}
(一般而言,我们通常建议对应用程序范围外的功能进行包装,从而能更好的封装他们)
上面的代码中我们把当前时间包装在我们自己写的代码中,因此调试就容易了些

public DateTime Now
{
get {
if (DEBUG)
return currentTime;
else
return DateTime.Now;
}
}
首先当只有在代码全部调用这个封装的Now,这个方法才有效,但是我们需要一种更加干净,更加面向对象化,同时可以实现相同功能的方法。
7.2Mock对象
有一种测试模式可以帮助我们 ,就是mock对象,mock对象也就是真是对象在调试期的替代品。
使用它来测试一共有三个关键步骤:
1 使用一个接口来描述这个对象
2 为产品代码实现这个接口
3以测试为目的,在mock对象中实现这个接口
因为被测试代码只会通过接口来引用对象,所以它就不知道引用的真实性了
我们对相面的代码改写
接口

public interface Environmental
{
DateTime Now
{
get;
}
//其他的方法都省略了
}
接下来我们编写真实的代码

public class MocksystemEnvironment:Environmental
{
private DateTime currenttime;
public MocksystemEnvironment(DateTime when)
{
currenttime = when;
}
public DateTime Now
{
get
{
return currenttime;
}
}
public void IncrementMinutes(int minutes)
{
currenttime = currenttime.AddMinutes(minutes);
}
}
注意我们给构造函数传入了一个时间参数,用作初始的当前时间制,并且构造函数用一个实例变量把它保存了下来,这个变量currenttime就是我们请求当前时间时所要返回的值,同事我们还提供了另一个方法,它会给这个时间加上给定的分钟数,这让你能够控制mock对象返回的日期和时间。
6.3正规化Mock Object
在。net中有多款Mock框架提供选择(http://www.mockobjects.org找到很多)这里我们看其中的一款DotNetMock
DotNetMock框架实际上是一个三合一
1.一个框架,让你有组织的创建mock对象
2.包含一小套预先定义的mock对象,可以拿来测试你的程序
3.还有一项技术,可以动态创建mock对象。
DotNetMock框架
对它了解之后,你会发现创建一个mock对象很简单,只不过是一些代码实现一个特殊的接口,返回你需要他返回的值
为了掩饰这一点,让我们看看如何使用DotNetMock框架来帮我们测试一个访问控制库,我们将从一个简单的类AccessControler开始,每个AcsessController对象负责控制一种资源,我们将把资源的名字传给构造函数,为了决定一个用户是否使用对象的资源, 我们调用对象的CanAccess函数传入用户名的名字和密码,这个类如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class AccessController
{
private Ilonger longer;
private string resource;
public AccessController(string resource, Ilonger longer)
{
this.longer = longer;
this.resource = resource;
longer.Setname("AccessControl");
}
public bool CanAccess(string user, string password)
{
longer.Log("Checking acces for"+user+"to"+resource);
if (password.Length == 0 || password == null)
{
longer.Log("missing password,Access denied");
return false;
}
//more checks
longer.Log("Access granted");
return true;
}
}
}
longer接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
interface Ilonger
{
void Setname(string name);
void Log(string msg);
}
}
我们可以很容易的创建接口mock对象,仅仅需要两个函数,一个用于设置正在longing的内容和名字,另一个用来log实际的消息

public class MockLoger1 : Ilonger
{
public void SetName(string name) { }
public void Log(string msg) { }
}
有了这些我们可以编写基本的单元测试了,在第一个测试中,将检测access controller是否在我们没有传入密码的情况下正确的拒绝了访问,

[TestFixture]
public class testAccessController
{
[Test]
public void MissingPassword()
{
MockLoger1 longer = new MockLoger1();
AccessController access = new AccessController("secrets",longer);
Assert.IsFalse(access.CanAccess("dave",null));
Assert.IsFalse(access.CanAccess("dave",""));
}
}
然而这个测试并没有验证我们的access controller 是否logging 了正确的消息,这就是mock能帮得上忙的地方,