DotNetMock源代码解析

 

DotNetMock库是我们使用较多的Mock库之一,目前已经能够支持NunitMBunit,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抽象了测试框架的行为,如:NUnitMbUnit等,通过Assertion来调用该框架,Assertion就是测试框架在DotNetMock中的代言人。Assertion使用Implementation来获得一个ITestFramework的实例,Implementation使用ImplementationFactory来加载这个实例,而ImplementationFactory首先通过配置文件,获取是否静态实现了ITestFramework,记载该静态的Assembly,否则通过动态机制先获得使用哪种测试框架(作者在这里使用判断方式,判断当前AppDomain域里面加载了哪个测试框架,如果加载了多个,按照优先级NunitMbunitCSUnit进行判断,判断接收后,分别创建对应的StubMaker对象,这个对象负责创建相应的实现代码),然后调用StubClassMaker来创建一个Assembly,通过SystemDynamicLinkerIDynamicLinker的实现)来加载该Assembly

动态Mock

       使用静态Mock的机制我们就已经能够建立Mock对象并进行测试了,这意味着我们要编写一个接口,一个实现该接口的Mock类,这样导致单元测试的代码量过于庞大。为此我们一般将静态Mock用于实现测试用的Mock框架,如:数据库测试用的Mock框架、安全测试用的Mock框架等,DotNetMock提供了一个这样的框架。

       为了解决上述问题,DotNetMock提供了动态Mock机制,这意味着我们编写单元测试代码时,只需要提供接口,不需要编写测试用的Mock代码。

类图

  
    
当我们通过IDynamicMock dynamicMockObjectnew DynamicMock(typeof(我们的接口));系统会自动调用ClassGenerator生成一个动态的Assembly,并生成一个“mock我们的接口”的实现类。通过IMyInterface myInstancemock.object来获得该实现类的一个对象,使用myInstance,通过使用mock.SetValue(方法名称,)来设置Mock对象中,某个方法的返回值。当我们调用该接口的方法时,DotNetMock会自动产生一个ExpectionMethod并使用MethodCall调用返回刚才设置的值。基本原理图如下:

 

      

       由于方法调用本身比较复杂,可能使用表达式方式调用,如:isReadOnly && IsCanAccess,首先要通过断言的Eval来判断参数是否与设置的值是否相等,从来进行调用。这一部分没有进行仔细研究,如有问题请及时反馈给我。


由于时间仓促,有些部分没有仔细研究,如果大家发现什么问题请及时反馈:
caidehui@21cn.com 或者QQ19646007 .

posted on 2005-10-11 17:34  caidehui  阅读(3013)  评论(2编辑  收藏  举报