Interface
在C#中的使用频率是非常高的,为了满足面向对象的需求,接口声明了对象所具有的行为,利用其可以实现具有可扩展性的类,例如可以通过返回接口,返回具体的类,而隐藏类的实现细节,并且仅仅暴露该接口所支持的属性和方法。又或者可以通过接口传递参数,利用多态实现系统的可扩展性。这里,我想演示的是,使用接口创建Mock对象。
首先解释下什么是Mock对象,这就好比拍电影的时候的替身演员,比如要完成一些高难度的动作,需要具有特殊技能的人代替真正的演员。Mock对象就是这个意思。在实际单元测试中,所需要的对象可能创建代价特别高或者不稳定,比如数据库连接,或者网络连接,这样一些不确定的因素在测试中会影响测试结果,在做团队开发的时候我们常常也可以听到这样的抱怨:“把服务器打开,我这边要测试..。”实际上测试的并不是底层连接,而是返回的数据是否能在被测的方法中正常运行。呵呵,或许您又有办法了,在方法里面注释掉从数据库读数据的部分,给变量手动赋值,Well good idea. 那么面对成千上万的代码,你完成了测试你还得把测试代码擦干净。 Oh, 下次测试怎么办?再加?#%#◎◎×。另外一点,有时候你想测的代码引用了还没开发出来的对象,仅仅是用它的接口。由此,你更需要用Mock来测试了。
下面说说怎么做:
1
.我们可以按照这个原理自己手写Mock对象,但是我们也可以使用工具,这里使用一个开源Mock框架DotNetMock(http://sourceforge.net/projects/dotnetmock)。
2.
首先,你应该估计出Mock对象是什么样子了。我们可以手动的写写这个对象。
假设有个类,它提供记录日志的功能,如下:
public
class
DataAccessor { ILogger _logger;
public
DataAccessor(ILogger logger) { _logger
=
logger; }
public
void
LogData() { _logger.SetName(
"
DataAccessor
"
); _logger.Log(
"
new log1
"
); _logger.Log(
"
new log2
"
); } }
我们创建了ILogger确定接口方法。并作为构造函数参数传入。ILogger接口如下
public
class
Logger : ILogger {
#region
ILogger Members
public
void
SetName(
string
name) {
return
; }
public
void
Log(
string
logVal) {
return
; }
#endregion
}
接着,写我们的MockLogger,我们把它放置在我们的测试类中,并放在一个不同的文件里,千万不要和产品代码放在一起从而导致客户的抱怨。然后,为了使用这个开源框架,我们添加引用DotNetMock, MockLogger不仅继承于ILogger,还继承于MockObject:
public
class
MockLogger : MockObject, ILogger {
private
ExpectationValue _name
=
new
ExpectationValue(
"
name
"
);
private
ExpectationArrayList _msgs
=
new
ExpectationArrayList(
"
msgs
"
);
public
string
ExpectedName {
set
{ _name.Expected
=
value; } }
public
void
AddExpectedMsg(
string
msg) { _msgs.AddExpected(msg); }
#region
ILogger Members
public
void
SetName(
string
name) { _name.Actual
=
name; }
public
void
Log(
string
logVal) { _msgs.AddActual(logVal); }
#endregion
}
按照这里的需要,我们用DotNetMock 提供的ExpectationValue和ExpectationArrayList用来存放Logger的名称以及它记录的字符串集合。并添加了两个方法。
最后,我们在Test框架中使用这个测试类,这里用的是NUnit:
[TestFixture, Category(
"
Full
"
)]
public
class
TestClass { [Test]
public
void
TestLog() { MockLogger logger
=
new
MockLogger(); DataAccessor acc
=
new
DataAccessor(logger); acc.LogData(); logger.ExpectedName
=
"
DataAccessor
"
; logger.AddExpectedMsg(
"
new log1
"
); logger.AddExpectedMsg(
"
new log2
"
); logger.Verify(); } }
那个Verify()方法帮我们完成了测试。OK,
我们的替身做好了,也测试了。可能真正的Logger还没开发呢,但是我们已经可以测试DataAccessor的方法了。同样的,我们可以创建数据库连接等Mock对象,但是等等,其实我们可以更好的使用这个DotNetMock框架,试试使用DotNetMock.Framework下的Data中已经有的Mock对象吧。
另外,可能你会问那么如果需要Mock的对象有几千个方法,我只用其中几个,那我不疯掉?其实有一种叫做动态Mock的对象是不需要你全部写满的。这里我就不介绍了,有兴趣的自己找资料吧。所有的信息均参照于《单元测试之道-C#版》,上面有更好的讲解,这里看到他的例子,想想接口还能有这样的好处,所以就写了这个文章,呵呵。