〇、目录一、前言二、需求说明三、准备工作四、开工1.编写测试用例与实现代码2.静态属性的模拟3.私有方法的测试4.私有方法的模拟五、总结六、源码下载一、前言上文书(基于VS2012Fakes框架的TDD实战——接口模拟)把接口模拟的部分演示完了,接口模拟也是Mock框架最基本的功能了吧,比如很易用的Moq框架,就非常容易模拟出接口中定义的操作返回的结果。Moq也有局限性,比如不能模拟密封类,不能直接模拟静态方法等,而这些需求在微软VS2012带来的Fakes框架中都能得到很好的解决。二、需求说明一个项目的开发中,最见怪不怪的就是需求的变更了,比如我们这个用户名重复性检查的功能,它就变了,变化如下:给未激活用户信息添加有效期属性,防止用户名被恶意占用三、准备工作修改MemberInactive类如下:1publicclassMemberInactive:Entity2{3publicstringUserName{get;set;}45publicstringPassword{get;set;}67publicstringEmail{get;set;}8激活过期时间11////summary12publicDateTimeExpiration{get;set;}13}四、开工1.编写测试用例与实现代码先编写一个检查未激活用户信息有效性的方法的测试用例,现在是2012年8月26日,所以定MemberInactive的过期时间为2012年8月27日。方便起见,我们先把IsMemberInactiveValid方法的可访问性定为public,用使可以用原来的方式来进行测试2.1[TestMethod]3.2publicvoidIsMemberInactiveValid_有效的_过期时间大于当前时间()4.3{5.4varmemberInactive=newMemberInactive{Expiration=newDateTime(2012,8,27)};6.5Assert.IsTrue(_accountService.IsMemberInactiveValid(memberInactive));6}在类AccountService编写IsMemberInactiveValid方法让测试通过1publicboolIsMemberInactiveValid(MemberInactivememberInactive)2{3vardtNow=DateTime.Now;4returnmemberInactive.Expiration.CompareTo(dtNow)=0;5}7.静态属性的模拟测试通过了,但上面的测试用例有个问题,今天能跑通过,后天呢,到了28号,就注定是失败的了,因为实现方法中有一个外部依赖DateTime.Now,自动化测试中,方法体范围内的所有外部依赖都应该被模拟即你要测的仅是这个方法内的代码的正确性,不应该受外界影响。,这是一个静态的公共属性。在mscorlib.dll程序集System命名空间下实现的。所以需要创建System的Fakes程序集。静态成员的模拟将用到Shim类型的模拟类(Fakes框架生成的模拟类有两种,Stub和Shim,具体请参考官方文档)修改上面的测试用例如下:1[TestMethod]2publicvoidIsMemberInactiveValid_有效的_过期时间大于当前时间()3{4varmemberInactive=newMemberInactive{Expiration=newDateTime(2012,8,27)};5//Assert.IsTrue(_accountService.IsMemberInactiveValid(memberInactive));6using(ShimsContext.Create())7{8ShimDateTime.NowGet=()=newDateTime(2012,8,26);9Assert.IsTrue(_accountService.IsMemberInactiveValid(memberInactive));10}11}第8行即模拟了DateTime.Now的返回值,这时,即使你把系统时间修改为28号,这个测试也能通过,因为现在测试的运行已经与系统时间无关了。8.私有方法的测试上面的例子为了承接上篇写测试用例的方法把IsMemberInactiveValid方法设成了public,但实际上这个方法应该是私有的,现在把方法的可访问性改为private,原来的测试用例当然是不能通过的,因为这个方法找不到了。把测试用例改为如下:[TestMethod]publicvoidIsMemberInactiveValid_有效的_过期时间大于当前时间(){varmemberInactive=newMemberInactive{Expiration=newDateTime(2012,8,27)};using(ShimsContext.Create()){ShimDateTime.NowGet=()=newDateTime(2012,8,26);varpo=newPrivateObject(newAccountService());varresult=po.Invoke(IsMemberInactiveValid,newobject[]{memberInactive});Assert.IsTrue((bool)result);}}测试通过,私有成员的访问用到了PrivateObject,其实这个类也没什么奇特的地方,只是封装了反射的相关操作,让我们调用更方便些9.私有方法的模拟在把调用IsMemberInactiveValid的代码加入UserNameExistsCheck方法之前,千万别忘记了在测试类初始化的代码中把IsMemberInactiveValid模拟出来,否则加入之后原来的测试用例就有可能无法通过了。下面这个测试用例就通不过了1[TestMethod]2publicvoidUserNameExistsCheck_用户存在_用户在用户数据库中不存在_and_注册需要激活_用户在未激活用户数据库中存在()3{4varuserName=柳柳英侠;5varconfigName=configName;6_member=null;7_configInfo.RegisterConfig.NeedActive=true;8Assert.IsTrue(_accountService.UserNameExistsCheck(userName,configName));9}不过这个问题先放下,我们先来看看私有成员应该怎样来模拟,将用到AccountService类的模拟类,因为这个私有方法是这个类的成员,如下的测试用例:1[TestMethod]2publicvoidUserNameExistsCheck_用户存在_用户在用户数据库中不存在_and_注册需要激活_and_用户在未激活用户数据库中存在_and_未激活用户信息有效()3{4varuserName=柳柳英侠;5varconfigName=configName;6_member=null;7_configInfo.RegisterConfig.NeedActive=true;8using(ShimsContext.Create())9{10ShimAccountService.AllInstances.IsMemberInactiveValidMemberInactive=(@accountService,@memberInactive)=true;11Assert.IsTrue(_accountService.UserNameExistsCheck(userName,configName));12}13}根据测试用例修改UserNameExistsCheck方法代码如下(第26行)1publicboolUserNameExistsCheck(stringuserName,stringconfigName)2{3if(string.IsNullOrEmpty(userName))4{5thrownewArgumentNullException(userName);6}7if(string.IsNullOrEmpty(configName))8{9thrownewArgumentNullException(configName);10}11varmember=MemberDao.GetByName(userName);12if(member!=null)13{14returntrue;15}16varconfigInfo=ConfigInfoDao.GetByName(configName);17if(configInfo==null)18{19thrownewNullReferenceException(系统配置信息为空。);20}21if(!configInfo.RegisterConfig.NeedActive)22{23returnfalse;24}25varmemberInactive=MemberInactiveDao.GetByName(userName);26if(memberInactive!=null&&IsMemberInactiveValid(memberInactive))27{28returntrue;29}30returnfalse;31}测试通过。现在来解决那个未通过的测试用例,按照TDD的原则,我们不能去修改测试用例来使它通过。不能通过的原因也就是IsMemberInactiveValid的模拟没有在测试类中进行初始化,下面我们来初始化它。由上面的测试用例可以看到,在私有成员的模拟中,测试方法的执行结果必须放在using语句中,而using语句实质也就是自动化了IDisposable接口,所以我们完全可以把它拆开,然后手动调用Dispose即可在测试类AccountServiceTest添加一个私有字段来存储ShimsContext.Create(),一个私有字段存储IsMemberInactiveValid的模拟结果:1privateIDisposable_shimsContext=ShimsContext.Create();2privatebool_isMemberInactiveValid=true;在标记[TestInitialize]的MyTestInitialize方法中添加IsMemberInactiveValid方法的模拟1ShimAccountService.AllInstances.IsMemberInactiveValidMemberInactive=(@accountService,@memberInactive)=_isMemberInactiveValid;取消标记[TestCleanup()]的MyTestCleanup方法的注释,添加ShimsContext.Create()的Dispose调用1//在每个测试运行完之后,使用TestCleanup来运行代码2[TestCleanup()]3publicvoidMyTestCleanup()4{5_shimsContext.Dispose();6}这样,初始化完毕,再运行全部测试用例,全绿,心情大好└(^o^)┘至此,我认为要讲的大概都讲到了,当然,Fakes框架还有很多功能,有待大家挖掘。五、总结总结说点什么呢,总结一下TDD吧开发过程:1.快速新增一个测试2.运行所有的测试(有时候只需要运行一个或一部分),发现新增的测试不能通过3.做一些小小的改动,尽快地让测试程序可运行,为此可以在程序中使用一些不合