基于TypeMock Isolator 隔离框架的单元测试
2013-09-11周建辉
周建辉
(南通纺织职业技术学院机电工程学院 江苏南通 226007)
0 引言
软件测试能使软件质量得到保障[1],而作为最基本的单元测试能发现大部分的软件故障,它在软件开发特别是大型软件系统开发中地位非常重要[2],它也为集成测试和系统测试的顺利进行奠定了基础。
从根本上说,单元测试就是针对系统的源代码编写测试代码,以判定某个特定条件下特定方法的行为是否符合预期。由于测试一个方法需要编写若干个测试方法,并且在有些情况下实现单元测试比较困难,所以单元测试工作量往往很大。
许多企业为了缩短项目开发周期,追求开发速度最大化,往往忽视了单元测试,直接进行功能测试,最后难以保证软件的高质量,反而为软件的纠错维护付出更高的成本。因此必须仔细分析单元测试面临的难题,寻找解决这些难题的途径,以提高测试效率,降低测试成本[3]。
1 单元测试面临的难题
在面向对象编程中,单元测试往往以类为单位,导致单元测试难以进行的原因有两个:
1)类依赖于一些其他难以进行人为控制的对象,例如文件系统、日期时间、Web 服务、Http-Conext 等,所以无法预期依赖项的返回值或行为,例如调用一个Web 服务的返回值根本无法预测,再例如在类中依赖于系统的当前时间,那么根本无法预测在测试代码执行时得到的当前时间。
2)类本身的设计不利于编写单元测试代码。例如被测试的方法被封装为私有的,那么在编写测试代码时就必须使用反射机制,增加了工作量。还有些方法调用层次很深,逻辑比较复杂,方法的返回结果不直观,导致对被测方法的预期结果无法轻易获得。
解决上述难题的传统方法是重构代码,而重构虽然能够使代码可测,但打破了原有代码的架构,不仅工作量巨大,而且无法保证不影响原有代码功能,更有甚者,有时被测代码根本不利于重构。
2 TypeMock Isolator 隔离框架[4]
能较好地解决上述难题的方法是应用隔离框架,隔离框架提供创建模拟对象和桩对象的一组可编程API。通过隔离框架的使用,可以相对容易解除与依赖对象的依赖,无需重构代码。对于大多数开发平台,都有相应的隔离框架,例如Java 有JMock 和EasyMock 等,.Net 有NMock、Moq、Rhino Mocks 和Typemock Isolator 等。
在这些隔离框架中Typemock Isolator 是一个非常强大的商业隔离框架,其工作原理如图1 所示。
图1 Typemock Isolator 工作原理
比起其他框架,该隔离框架有如下优点:
1)无论系统是如何设计,都可以通过一组简洁的API 调用将类和它的依赖隔离,它不强迫设计是可测试的,这是它最为突出的优点。
2)覆盖率分析可以立刻让测试人员了解到哪些代码已经被覆盖,哪些代码尚未被单元测试覆盖,从而不会遗漏未被测试的代码。
3)该工具与其他的开发工具能够很好地集成,例如Visual studio、VSTS、dotCover.。
3 应用
本文研究的单元测试针对南通中小企业IT 外包公共服务技术平台,该平台基于开源框架,系统架构复杂,由数据访问层项目、表示层项目、辅助项目、第三方组件项目等多个项目组成,大量代码依赖于配置文件、数据库、Web 服务、网络资源等,代码调用关系复杂,调用层次较深,在经过一系列单元测试工具的对比分析和实践检验后,隔离框架Typemock Isolator 圆满地解决了单元测试过程中遇到的难题。
3.1 测试环境搭建
搭建测试环境时操作系统选用Windows7,测试代码编写工具采用Visual Studio.Net 2010。然后在原有的平台解决方案中添加测试项目,用于撰写和运行测试代码,并在该项目中重新创建配置文件APP.config。
在单元测试中引入隔离框架Typemock Isolator7.0 之外,还需引入测试工具Test Driven.Net3.5 和单元测试框架NUnit[5]Test Driven .Net 是以插件的形式集成在Visual Studio[6],它可以以调试模式运行测试代码,在无法确定到底是测试代码还是被测代码引发的测试不通过时,该模式极为有效。
图2 是平台单元测试所用到的组件部署架构。
图2 测试环境组件部署图
3.2 测试重点与测试组织
平台解决方案中项目众多,代码更是不计其数,虽然采用了相关工具和框架,但是单元测试的工作量仍然巨大,必须有重点地进行单元测试。
由于框架本身是开源的,经过广大志愿者的不断完善,已经相对成熟,故障较少,质量较高,所以作为次重点,而扩展项目和代码则是由项目组开发人员编写,项目时间比较紧,难免会存在故障,所以作为重点。
因为测试代码很多,需要较好的组织测试代码[7]。在组织代码时,采用如下几个方法:
1)针对每个被测项目,在测试项目中建立相应的文件夹,文件夹的命名方法为被测项目名+”.UnitTest”,例如平台解决方案中包含WebStore.Business 项目,那么对应的测试文件夹为Web-Store.Business.UnitTest。
2)针对每个被测项目下的类文件,在测试项目中建立相应的测试文件,文件名的命名规则为被测文件名+”Test”+”.cs”,为了避免被测项目和测试项目的命名空间冲突,将测试文件默认的命名空间名改为测试项目名+去掉'.'的所在文件夹名+”UnitTest”。例如要测试WebStore.Business 下的Coupon.cs,则在相应的测试文件夹下创建测试类文件CouponTest.cs,该类所在的命名空间为Web-StoreNunitTest.WebStoreBusinessUnitTest。
3)在测试文件中包含一个自动创建的类,在该类中会创建许多用于测试的方法,测试方法的命名规则为:public void 被测方法_Arrange 描述_Assert描述();其中Arrange 描述是指需要为该测试方法准备的数据、场景和条件等的描述,而Assert 描述是指执行被测方法后的返回值、可能产生的异常等的描述。例如测试方法
public void GetAllDisplayByOfferGuidAnd-Type_OfferGuidNotExists_ReturnNoRecord();表示被测试的是GetAllDisplayByOfferGuidAndType方法,测试条件为指定的OfferGuid 在数据库中不存在,返回结果应该为空记录。
3.3 典型案例
以本平台的母版页layout 的Page_Load 方法单元测试为例。该类方法的定义如下:
在对该方法进行单元测试时,存在如下一些困难。
语句1 声明的是一个protected 方法,该方法是私有的,不能通过对象直接调用;语句2 中的Http-Context 属性是Web 程序在运行时的Http 上下文环境,只有在运行时该属性才有意义,在单元测试时根本无法预测该属性的行为;语句3 调用的方法GetCurrentSiteSettings()依赖Http 上下文环境、服务器缓存和MySql 数据库,调用层次深,逻辑比较复杂;语句4 调用的方法SiteUtils.GetSkinBaseUrl 与Http 上下文环境相关;语句5 中调用的CacheHelper.GetCurrentPage()依赖于HttpContext 和数据库。
通过传统的代码重构、接缝等技术,不仅工作量巨大,而且难以管理,实际是行不通的。其他的一些隔离框架在使用时或多或少地存在一些缺陷,而采用Typemock Isolater 则非常容易实现单元测试。
这个被测方法的一个测试用例,如表1 所示。
表1 Page_Load 被测方法的一个测试用例
针对该测试用例的完整的测试代码如下:
语句1 创建一个HttpContext 伪对象,语句2设置HttpContext 静态属性Current 的值为语句1创建的伪对象,语句4 设置方法调用CacheHelper.GetCurrentSiteSettings()的返回值为null,语句5设置以任意参数调用方法SiteUtils.GetSkinBaseUrl的返回值为”http://127.0.0.1”,语句6 创建测试对象,语句7~9 构建调用类layout 的方法Page_Load 传入的参数,语句11 设置对象layoutobject.Page 属性的返回值,语句12 设置CacheHelper.GetCurrentPage 的返回值,语句13 调用layoutobject 的保护方法Page_Load,语句14 确认Cache-Helper.GetCurrentPage()被调用,语句15 确认layoutobject.FindControl("")未被调用。
分析一下,可以发现不管代码依赖什么,都只需要通过一组简单的API 调用完成伪对象的创建、属性值的设置和方法返回值的设定。代码低耦合,高内聚,几行代码就完成了单元测试代码的编写,无须重构,无须接缝。
3.4 运行
在.Net 中,可以方便地执行测试代码,测试代码的缩略图如图3 所示。
右键单击图1 中左上角的[Test,Isolated],会弹出如图4 所示的菜单。
图3 测试代码略图
图4 运行菜单
点击Run Test(s)菜单项,测试代码自动执行,上述测试代码的执行结果如图5 所示。
图5 测试结果
4 结束语
通过实践可以证明,应用TypeMock Isolator隔离框架可以方便地创建模拟对象和桩对象,撰写单元测试代码简单易读,结合其他测试工具,可以非常轻松地完成单元测试任务,较好地解决了单元测试面临的问题[8],实现了一定程度的测试自动化[9]。不过该框架是商业框架,需要付费,一定程度上限制了该框架的推广使用。
[1]陈静.单元测试在软件开发过程中的作用[J].舰船电子对抗,2006(3):63-65.
[2]王鹏,习媛媛,马丽,等.单元测试在软件质量保证中的应用研究[J].山西财经大学学报,2009(2):217-218.
[3]高共革,杨静.基于.Net 的单元测试自动化方法研究[J].微计算机信息,2008(19):280-281.
[4]刘赟.基于模拟对象Mock Object 的单元测试研究[J].电脑知识与技术,2008(5):878-881.
[5]林勤花.使用NUnit 在.net 编程中进行单元测试[J].科技信息,2008(24):410-411.
[6]刘娟,李红生.TestDriven.Net 在软件系统单元测试中的应用[J].信息技术与信息化,2010(5):61-63.
[7]蔡高亮.软件单元测试[J].信息技术与标准化,2008(1-2):41-43.
[8]马小龙.C 测试项目在数据库单元测试中的应用[J].电脑编程技巧与维护,2010(4):64-66.
[9]王卫霞.一种基于控制流的web 应用系统测试方法的探讨[J].常州信息职业技术学院学报,2008(6):18-20,61.