接口的另一用法-构建单元测试的Mock对象

      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#版》,上面有更好的讲解,这里看到他的例子,想想接口还能有这样的好处,所以就写了这个文章,呵呵。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值