DotNetMock源代码解析
DotNetMock库是我们使用较多的Mock库之一,目前已经能够支持Nunit、MBunit,CSUnit等多个测试框架,结合这些测试框架,能够非常方便的测试那些不太方便创建的对象、或者该对象具有比较复杂的不太确定的行为的对象。该框架也被许多开源的框架所使用,为此我门简单的分析了它的源代码,大致搞清楚它的实现过程。
Mock的基本原理
类图
每个需要Mock的类都需要实现IMockObject或者从MockObject继承。
当MockObject及其之类的对象掉用Verify时,将调用Verfier.Verify(),该函数首先取得检查对象的父对象,进行检查,然后检查每个Field,代码如下:
private static void verify(Object verifiableObject, Type currentType)
{
if (isRootType(currentType))
{
return;
}
verify(verifiableObject, currentType.BaseType);
FieldInfo[] fields = currentType.GetFields(_bindingFlags);
///检查每个Field
foreach (FieldInfo field in fields)
{
verifyField(field, verifiableObject);
}
///将已经检查过的对象记入已检查对象列表。
_verifiedObjects.Add( verifiableObject.GetHashCode() );
}
isRootType:如果为类型为Object时,到达根对象,就不用再检查了。
检查某个Field时,如果该Field也是可检查对象,则检查,否则不做检查,代码如下:
Private static void verifyField(FieldInfo verifiableField, Object verifiableObject)
{
Object fieldValue = verifiableField.GetValue(verifiableObject);
IVerifiable aVerifiable;
if ((aVerifiable = fieldValue as IVerifiable) != null)
{
if ( ! aVerifiable.IsVerified )
{
aVerifiable.Verify();
}
}
}
代码aVerifiable.Verify()循环上面的调用过程。
测试框架支持
这部分比较复杂,大致思路是这样子,当你需要判断期望的值与执行后的值是否相等时,在NUnit中可以使用Assertion.AssertEquals(“”,ExpectionValue,ActualValue),而在MBUnit中没有直接对应的方法,为此我们需要先定义一个接口,然后针对不同的测试框架实现这个接口,为了避免直接和框架耦合,可以使用配置文件,通过Assembly.Load 等方式加载这个框架。这个方法就是DotNetMock使用的框架静态加载机制。
DotNetMock没有为我们提供这些框架的加载代码,可能DotnetMock的作者认为太简单了,并且为每个框架只作一个Assembly实在很丑陋,为此他又提供了一个动态机制来自动生成一个动态的Assembly,这种方法使用了MSIL的高级方法,基本的原理图如下:
其中,ITestFramework抽象了测试框架的行为,如:NUnit,MbUnit等,通过Assertion来调用该框架,Assertion就是测试框架在DotNetMock中的代言人。Assertion使用Implementation来获得一个ITestFramework的实例,Implementation使用ImplementationFactory来加载这个实例,而ImplementationFactory首先通过配置文件,获取是否静态实现了ITestFramework,记载该静态的Assembly,否则通过动态机制先获得使用哪种测试框架(作者在这里使用判断方式,判断当前AppDomain域里面加载了哪个测试框架,如果加载了多个,按照优先级Nunit、Mbunit、CSUnit进行判断,判断接收后,分别创建对应的StubMaker对象,这个对象负责创建相应的实现代码),然后调用StubClassMaker来创建一个Assembly,通过SystemDynamicLinker(IDynamicLinker的实现)来加载该Assembly。
动态Mock
使用静态Mock的机制我们就已经能够建立Mock对象并进行测试了,这意味着我们要编写一个接口,一个实现该接口的Mock类,这样导致单元测试的代码量过于庞大。为此我们一般将静态Mock用于实现测试用的Mock框架,如:数据库测试用的Mock框架、安全测试用的Mock框架等,DotNetMock提供了一个这样的框架。
为了解决上述问题,DotNetMock提供了动态Mock机制,这意味着我们编写单元测试代码时,只需要提供接口,不需要编写测试用的Mock代码。
类图
当我们通过IDynamicMock dynamicMockObject=new DynamicMock(typeof(我们的接口));系统会自动调用ClassGenerator生成一个动态的Assembly,并生成一个“mock我们的接口”的实现类。通过IMyInterface myInstance=mock.object来获得该实现类的一个对象,使用myInstance,通过使用mock.SetValue(方法名称,值)来设置Mock对象中,某个方法的返回值。当我们调用该接口的方法时,DotNetMock会自动产生一个ExpectionMethod并使用MethodCall调用返回刚才设置的值。基本原理图如下:
由于方法调用本身比较复杂,可能使用表达式方式调用,如:isReadOnly && IsCanAccess,首先要通过断言的Eval来判断参数是否与设置的值是否相等,从来进行调用。这一部分没有进行仔细研究,如有问题请及时反馈给我。
由于时间仓促,有些部分没有仔细研究,如果大家发现什么问题请及时反馈:caidehui@21cn.com 或者QQ:19646007 .