VS 2013之后,怎么对类的私有成员员做单元测试

VS 2013之后,怎么对私有成员做单元测试
[问题点数:40分]
VS 2013之后,怎么对私有成员做单元测试
[问题点数:40分]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2016年1月 总版技术专家分月排行榜第二2015年11月 总版技术专家分月排行榜第二2015年10月 总版技术专家分月排行榜第二
优秀小版主
2016年6月 .NET技术大版内专家分月排行榜第二2016年3月 .NET技术大版内专家分月排行榜第二2016年1月 .NET技术大版内专家分月排行榜第二2015年12月 .NET技术大版内专家分月排行榜第二2015年2月 .NET技术大版内专家分月排行榜第二2015年1月 .NET技术大版内专家分月排行榜第二2014年11月 .NET技术大版内专家分月排行榜第二2014年5月 .NET技术大版内专家分月排行榜第二2014年4月 .NET技术大版内专家分月排行榜第二2012年2月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二
2016年5月 .NET技术大版内专家分月排行榜第三2016年4月 .NET技术大版内专家分月排行榜第三2016年2月 .NET技术大版内专家分月排行榜第三2015年10月 .NET技术大版内专家分月排行榜第三2015年9月 .NET技术大版内专家分月排行榜第三2015年7月 .NET技术大版内专家分月排行榜第三2015年6月 .NET技术大版内专家分月排行榜第三2015年5月 .NET技术大版内专家分月排行榜第三2015年4月 .NET技术大版内专家分月排行榜第三2014年12月 .NET技术大版内专家分月排行榜第三2014年10月 .NET技术大版内专家分月排行榜第三2014年9月 .NET技术大版内专家分月排行榜第三2014年1月 .NET技术大版内专家分月排行榜第三2013年12月 .NET技术大版内专家分月排行榜第三2013年10月 .NET技术大版内专家分月排行榜第三2013年5月 .NET技术大版内专家分月排行榜第三2011年9月 .NET技术大版内专家分月排行榜第三2011年2月 .NET技术大版内专家分月排行榜第三2010年2月 .NET技术大版内专家分月排行榜第三
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。单元测试的基本概念和核心技法
单元测试的基本概念和核心技法
2.1 良好的单元测试——定义
我们已经了解了程序员需要单元测试, 下面我们来给单元测试作一个完整的定义:
定义: 单元测试是一段自动执行的代码, 它调用被测类或被测方法, 然后验证关于被测类或被测方法逻辑行为的假设确实成立. 单元测试几乎总是用单元测试框架(unit testing framework)来写就的, 单元测试是易于写就、执行快速、完全自动化、值得依赖、易于阅读并易于维护的.
这个定义有点长, 但是它却包含了大量重要信息:
单元测试的测试重点是被测类或被测方法的逻辑行为. 所谓&逻辑行为&, 指的是含有诸如判断、循环、选择、计算或其他决策过程的代码. 单元测试框架是辅助程序员写就单元测试的利器. 单元测试的代码本身, 同被测代码一样, 也应该是值得依赖、易于阅读并易于维护的.
有了单元测试的定义, 我们来看看有关单元测试的几个基本概念, 这些基本概念会在后面的章节中反复出现.
被测类(Class Under Test)和被测方法(Method Under Test): 顾名思义, 就是测试代码所操练的类或方法. 测试类(Test Fixture)和测试方法(Test Method): 负责操练被测类和被测方法. Test Method一般都是Test Fixture的成员函数.
测试运行器(Test Runner): 负责自动执行测试类中的测试方法. Test Runner可以是命令行界面的, 也可以是GUI的.
2.2 进行单元测试的核心技术和核心手法
2.2.1 核心技术
前面已经讲到, 单元测试所针对的目标是&单个&类或&单个&方法(这里的&方法&指C++中的自由函数). 这表明我们在单元测试中要做的主要任务是创建出被测类的对象, 并调用该对象的被测方法. 但是我们都知道, 一个类几乎不可能完全不依赖于其他类. 这种类之间的依赖会导致我们无法顺利地将一个被测类纳入单元测试覆盖这下, 因为单元测试需要的是对被测类这一个类的测试, 而不是同时测试被测类和它的合作者类.
因此, 我们在把ClassUnderTest纳入单元测试时, 也就必须先把ClassUnderTest与CollaboratorClass之间的依赖&打破&, 这个过程称为&解依赖&(dependency-breaking). 解依赖就是进行单元测试的核心技术. 解依赖的目标是希望能把不可控的CollaboratorClass替换成由我们控制的伪合作者类(FakeCollaboratorClass), 并使被测类能方便地选择是依赖于真实的合作者类还是伪合作者类. 对于产品代码, 被测类依赖的是真实的合作者类,
而在单元测试中, 被测类依赖的是由我们控制的伪合作者.
在单元测试代码中使用可控的FakeCollaboratorClass, 这给我们带来了两个便利:
我们可以通过FakeCollaboratorClass向ClassUnderTest返回任何我们指定的结果. 我们可以通过FakeCollaboratorClass来感知ClassUnderTest所做的动作.
这实际上就是FakeCollaboratorClass的两种表现形式:
Stub: 用于向ClassUnderTest返回我们指定的结果. Mock: 用于感知ClassUnderTest的行为.
我们明白了&解依赖&是单元测试的核心技术. 那么具体怎样实现解依赖呢? 下面我们就来介绍4种相关的核心手法, 其中前2种与CollaboratorClass有关, 后2种与ClassUnderTest有关, 这4种手法对于解决绝大多数的解依赖问题都适用.
2.2.2 &接口提取&和&Virtual and Override&
&接口提取&手法是对CollaboratorClass提取出一个抽象接口CollaboratorService, 然后让CollaboratorClass和FakeCollaboratorClass都去实现这个接口, 而ClassUnderTest则由直接依赖CollaboratorClass转向依赖于抽象接口CollaboratorService, 如下图所示.
实际上, &接口提取&手法是一种非常好的手法, 它使得我们的代码遵循&依赖抽象原则&, 遵循这个原则的软件具有较好的灵活性, 这是具有可测试性的软件也具有较好的设计的一个佐证.
而&Virtual and Override&手法则是使CollaboratorClass中的被依赖方法成为virtual, 然后让FakeCollaboratorClass去公有继承CollaboratorClass, 并且override那些虚函数, 从而替换掉 CollaboratorClass中的行为. 这种手法如下图所示.
总体上讲, 我们推荐优先使用&接口提取&手法, 因为这将使代码遵循&依赖抽象原则&, 从而使软件更好地应对今后的变化. 但是, &Virtual and Override&方法也是有其一席之地的, 我们后面会看到例子.
2.2.3 &参数注入&和&Extract and Override&
在ClassUnderTest这边, 对CollaboratorClass的依赖的产生方式也可以划分成两类:
依赖是通过方法参数传入的, 这种形式的依赖被称为&参数注入&式依赖(parameter injection dependency). 参数注入式依赖是一种耦合度较低的依赖产生形式, 因此对ClassUnderTest的影响不大, 一般最多只需要把方法的签名由直接依赖CollaboratorClass改成依赖接口CollaboratorService.
依赖是在被测方法的方法体内部产生的, 这种依赖被称为&隐藏式&依赖(hidden dependency). 隐藏式依赖有多种表现形式:
o 直接创建CollaboratorClass对象作为局部变量或成员变量.
o 通过一个工厂方法来产生CollaboratorClass对象.
o 通过一个工厂类来产生CollaboratorClass对象.
隐藏式依赖是一种耦合程度较高的依赖, 因此是我们着重需要&打破&的依赖. 一种方法是把隐藏式依赖转变成参数注入式依赖, 我们将在后面的小节中看到这种方法的应用. 而另一种方法, 则是使用&Extract and Override&手法, 即: 我们给ClassUnderTest引入一个virtual的工厂成员函数, 来返回一个CollaboratorClass对象的引用, 然后对ClassUnderTest派生出一个子类, 在该子类中override这个工厂成员函数, 让它返回一个FakeCollaboratorClass对象的引用,
如下图所示.
这里的TestingClassUnderTest被称为&测试用子类&(Testing Subclass). 这时, 在Test Fixture中被实例化的其实就是测试用子类, 而不是被测类本身.
以上我们研究了进行单元测试所需要的核心技术, 以及4种最常用的核心手法, 这些手法足以应付绝大多数的情况, 但是, 仍然有一些特殊情况需要我们特别注意, 我们从下一节开始, 以Q&A的形式, 讨论这些特殊情况.
2.3 应付构造函数中的隐藏式依赖
当ClassUnderTest中出现隐藏式依赖时, 最常用的有两种手法来打破这种依赖. 我们分别来看一下.
2.3.1 转变成参数注入式依赖
对于像C#和Java这样的语言, 由于它们支持在一个构造函数中去调用另一个构造函数, 因此可以很方便地增加一个构造函数, 把隐藏式依赖转变成参数注入式依赖. 下面的UML图阐释了这种手法.
而对于C++, 由于它没有提供在构造函数中调用另一个构造函数的功能, 因此通常的作法就是把公共的的初始化代码放入一个init()私有方法中去, 让不同的构造函数去调用init()方法.
2.3.2 使用&调包方法&
还可以考虑给ClassUndertTest引入一个&调包方法&, 使得测试类和测试方法可以很方便地将合作者类&调包&成伪合作者类. 这里的&调包方法&本质上就是一个setter方法, 但是为了表明这个特殊的setter方法只应该被测试类和测试方法调用, 我们一般给调包方法命名为SupersedeCollaborator(). 下图就是一个演示.
这里必须要提醒的是, 在C++中使用这个手法时必须注意在&调包方法&中不能引起资源泄漏, 也就是说, 必须先释放掉原有的合作者对象, 再以伪合作者替代之.
2.4 怎样测试ClassUnderTest中的private方法?
如果要测试一个类的private方法, 答案的总体思路是: 适当打破访问权限. 也就是说, 我们可以把private方法声明成protected方法, 然后对ClassUnderTest进行派生, 得到一个&测试用子类&, 在该&测试用子类&中声明一个public方法, 该方法是ClassUnderTest中的protected方法的代理. 这种手法可以用下图来表示.
2.4 应付&全局依赖&
所谓&全局依赖&, 是指被测类或被测方法所依赖的合作者是一个&全局单件&, 包括C#/Java/C++中的&单件类&(Singleton)和C++中的全局变量和自由方法(实际上, Singleton可视为全局变量在面向对象语言中的变种). 我们下面来看看怎么应对这些情况.
2.4.1 使用&封装全局引用&手法来解除对全局变量和自由方法的依赖
要打破对全局变量和自由方法的依赖, 其实基本思想就是: 把它们纳入到一个类中, 让它们成为一个类的成员变量和成员方法. 一旦把全局变量和自由函数封装进了某个类, 那么我们就可以继续利用2.2.2节提到的两种手法来引入伪合作者了.
2.4.2 使用&静态调包方法&来解除对单件类的依赖
单件类往往具有3个特点:
(1) 单件类的构造函数通常被设为private.
(2) 单件类具有一个静态成员变量, 该成员变量持有该类的唯一一个实例.
(3) 单件类具有一个静态方法, 用来提供对单件实例的访问, 通常该方法名叫getInstance().
由于单件类被设计成强制性地在整个程序中只有一份, 因此要伪造它比较困难. 我们推荐使用&静态调包方法&手法. 这个手法的本质是适当打破单件类的单件性约束, 把单件类的构造函数改为protected, 使我们能够从单件类派生出一个Fake子类, 然后为单件类引入一个静态调包函数SupersedeInstance(), 以允许单件类中的静态成员变量被&调包&成Fake子类对象. 下图表明了这一手法.
同样必须强调的是, 在C++中的使用这个手法的时候, 必须保证没有资源泄漏.
2.5 如果CollaboratorClass本身就处于继承体系中, 怎么办?
先设想一下CollaboratorClass本身存在基类的情况, 如下图所示.
如果使用&Virtual and Override&手法来伪造合作者类, 那么不存在任何问题, 我们可以用下面的图来表示.
另一方面, 如果想使用&接口提取&手法的话, 那么一种比较好的策略是使用&外覆类&(Wrapper Class), 如下图所示.
2.6 当CollaboratorClass是标准类库的一员时, 怎么办?
有些时候, 我们的被测类所依赖的是特定操作系统(Windows, Unix, 或Mac)、特定标准规范(.NET, 或J2EE)、特定函数库或类库(如POSIX API和Win32 API)及特定用户界面(CUI或者GUI)所提供的功能时, 这实际上是引入了对特定平台的依赖性, 而这往往都是在提示我们: 应该加入一个更高层次的抽象(也即一个间接层, indirection), 从而将这种对特定平台的依赖隐藏到这个抽象之后. 换句话说, 我们应该引入一个接口, 来使我们的代码具有平台无关性, 如下图所示.
2.7 怎样测试抽象接口?
假设我们的系统中定义了一个抽象接口ServiceInterface,系统中有两个类(分别是ServiceImpl1和ServiceImpl2)实现了这个接口。现在,我们希望为ServiceInteface抽象接口编写一个通用的测试类,这个测试类不仅能测试当前已经实现该接口的类,而且可以不加修改地应用于将来实现ServiceInteface接口的类。应该怎么办呢?下图展示了一种可能的方案。
上图中,ServiceInterfaceTestFixture测试类中的测试方法都是基于ServiceInterface来进行测试的,不依赖于其具体实现类。这样就保证了仅测试抽象接口所定义的行为。当将来系统引入ServiceInteface的新的实现类时,只需要从ServiceInterfaceTestFixture类再派生出一个新子类,并实现createServiceInstance()方法来创建相应的对象即可。
我的热门文章
即使是一小步也想与你分享IBM Bluemix
点击按钮,开始云上的开发!
developerWorks 社区
本文将介绍两种开发实践,用于提高 Java 单元测试中的代码覆盖率。代码覆盖率 = (被测代码 / 代码总数)*
100%。提高被测代码数量或降低代码总数,均可达到提高代码覆盖率的效果。在本文中,您将看到如何通过使用反射机制,在外部直接对目标类中的不可访问成员进行测试,以提高被测代码数量;以及通过修改 Cobertura 源码,使其支持通过正则表达式来过滤不需要进行单元测试的代码,以降低代码总数。代码覆盖率的提高,减少了单元测试过程中未被覆盖的代码数量,降低了开发人员编写或修改单元测试用例的时间成本,从而提高了整个单元测试的效率。
, 软件工程师,
王传阳,软件工程师,计算机软件硕士,就职于 IBM 中国开发中心宁波分公司,目前从事 J2EE 项目开发与客户支持工作 , 对 IBM 产品及开源软件研究方面有深入研究。
, 软件工程师,
刘伏亮,软件工程师,就职于 IBM 中国开发中心宁波分公司,专注于 JAVA 性能测试与单元测试框架开发,并成功将自行开发的单元测试框架应用于部门项目中。
引言单元测试是软件开发过程中重要的质量保证环节。单元测试可以减少代码中潜在的错误,使缺陷更早地被发现,从而降低了软件的维护成本。软件代码的质量由单元测试来保证,而单元测试自身的质量与效率问题也不容忽视。提高单元测试的质量与效率,不仅能够使软件代码更加有保证,而且能够节省开发人员编写或者修改单元测试代码的时间。衡量单元测试质量与效率的指标多种多样,代码覆盖率是其中一个极为重要的指标。一般而言,代码覆盖率越高,单元测试覆盖的范围就越大,代码中潜在错误的数量就越少,软件质量就越高。本文首先介绍代码覆盖率的统计指标类型及常用统计工具,然后重点选取具有代表性的行覆盖率进行分析,介绍两种方法用于提高代码的行覆盖率。代码覆盖率的统计指标代码覆盖率指的是一种衡量代码覆盖程度的方式,通常会对以下几种方式进行统计分析: 行覆盖。它又被称作语句覆盖或基本块覆盖。这是一种较为常用且具有代表性的指标,度量的是被测代码中每个可执行语句是否被执行到。
条件覆盖。它度量的是当代码中存在分支时,是否能覆盖进入分支和不进入分支这两种情况。这要求开发人员编写多个测试用例以分别满足进入分支与不进入分支这两种情况。
路径覆盖。它度量的是当代码中存在多个分支时,是否覆盖到分支之间不同组合方式所产生的全部路径。这是一种力度最强的覆盖检测,相对而言,条件覆盖只是路径覆盖中的一部分。在这三种覆盖指标中,行覆盖简单,适用性广,但可能会被认为是“最弱的覆盖”,其实不然。行覆盖相对于条件或路径覆盖,可以使开发人员通过尽可能少的测试数据和用例,覆盖尽可能多的代码。通常情况下,是先通过工具检测一遍整个工程单元测试的行覆盖情况,然后针对没有被覆盖到的代码,分析其没有被覆盖到的原因。如果是由于该代码所在分支由于不满足进入该分支的条件而没有被覆盖,那么开发人员才会进一步修改或增加测试代码,完成该部分的条件或路径覆盖。可见,高效高质量的行覆盖是有效进行条件覆盖与路径覆盖的前提。行覆盖率越高,说明没有被覆盖到的代码越少,这样开发人员便会集中精力修改测试用例,覆盖这些数量不多的代码。相反,如果行覆盖率低,开发人员需要逐个检查没有被覆盖到的代码,精力被分散,因此很难提高剩余代码单元测试的质量。代码覆盖率 = 被测代码行数 / 参测代码总行数 * 100%。
从代码覆盖率的计算方式中可以看出,要提高代码覆盖率,可通过提高被测代码行数,或减少参测代码总行数的方式进行。以下将会从这两个角度分别入手,分析如何提高被测代码行数及减少参测代码总行数。使用 Cobertura 统计并提高代码的行覆盖率Cobertura
是一款优秀的开源测试覆盖率统计工具,它与单元测试代码结合,标记并分析在测试包运行时执行了哪些代码和没有执行哪些代码以及所经过的条件分支,来测量测试覆盖率。除了找出未测试到的代码并发现
bug 外,Cobertura 还可以通过标记无用的、执行不到的代码来优化代码,最终生成一份美观详尽的 HTML 覆盖率检测报告。Cobertura 基本工具包里有四个基本过程及对应的工具:cobertura-check, cobertura-instrument, cobertura-merge,
cobertura- 这个脚本独立使用较为繁琐,不方便也不利于自动化。不过, Cobertura 在 Maven 编译平台上有相应的
cobertura-maven-plugin 插件,使代码编译、检测、集成等各个周期可以流水线式自动化完成。Cobertura-maven-plugin 官方版有五个主要目标指令 (goal),如表 1:表 1. Cobertura 目标指令及作用解释 目标指令
Cobertura:check
检查最后一次标注(instrumentation) 正确与否
Cobertura:clean
清理插件生产的中间及最终报告文件
Cobertura:dump-datafile
Cobertura 数据文件 dump 指令 , 不常用
Cobertura:instrument
标注编译好的 javaclass 文件
Cobertura:cobertura
标注、运行测试并产生 Cobertura 覆盖率报告 Cobertura 通常会与 Maven 一起使用。因此工程目录结构如果遵循 Maven 推荐的标准的话,一个集成 Cobertura 的基本 POM 文件如清单 1
所示:清单 1. POM 文件的基本结构&project&
&reporting&
&!-- 此处用于将 Cobertura 插件集成到 Maven 中 --&
&groupId&org.codehaus.mojo&/groupId&
&artifactId&cobertura-maven-plugin&/artifactId&
&version&2.5.2&/version&
&/plugins&
&/reporting&
&/project&如果工程目录结构没有采用 Maven 推荐标准,则需要进行如下额外设置:清单 2. 适合 Maven 的工程目录结构配置&build&
&!-- Java 源代码的路径配置 --&
&sourceDirectory&src/main/java&/sourceDirectory&
&scriptSourceDirectory&src/main/scripts&/scriptSourceDirectory&
&!-- 测试代码的路径配置 --&
&testSourceDirectory&src/test/java&/testSourceDirectory&
&!-- 源码编译后的 class 文件的路径配置 --&
&outputDirectory&target/classes&/outputDirectory&
&!-- 测试源码编译后的 class 文件的路径配置 --&
&testOutputDirectory&target/test-classes&/testOutputDirectory&
&plugin& .... &/plugin&
&/build&单元测试代码编写完成,所有设置配制好后,在工程根目录运行“mvn cobertura:cobertura”Maven
就会对代码进行编译。编译完成之后,就会在项目中运行测试代码并输出测试报告结果到目录
project_base$\target\site\cobertura\index.html,效果如图 1 所示。图 1. Cobertura 覆盖分析报告从以上报告中可见, 代码整体的行覆盖率并不高,有些包或类覆盖率很低,甚至为
0。考虑到这些包或类的特殊性(例如它们已被其他类取代),无需对它们进行单元测试,因此需要从整个测试范围中剔除。 部分类的行覆盖率虽然已接近 100%,但仍存在一些方法(如 set 和 get 方法)由于没有测试的必要却被列入了统计范围,这些方法需要被过滤掉。针对上述两种改进措施,都可以使用 Cobertura 进行实现。第一种改进措施 Cobertura 可以支持,而第二改进措施则需要对 Cobertura
源码进行修改,重编译后方可支持。下面将详细介绍如何使用 Cobertura 对上述问题进行优化。过滤不需进行单元测试的包和类针对项目中不需进行单元测试的包和类,我们可以利用 POM 文件中 Cobertura 的标注 (instrument) 设置,对相应的包和类进行剔除 (exclude)
或筛选 (include),使之不体现在覆盖率报告中,去除它们对整个覆盖率的影响,从而使报告更具针对性。其基本 POM 标签设置及解析如清单 3 中所示。清单 3. POM 中剔除包和类的设置示例&configuration&
&instrumentation&
&excludes&
&!--此处用于指定哪些类会从单元测试的统计范围中被剔除 --&
&exclude&exs/res/process/egencia/Mock*.class&/exclude&
&exclude&exs/res/process/test/**/*Test.class&/exclude& &/excludes&
&/instrumentation&
&/configuration&
&executions&
&execution&
&goal&clean&/goal&
&/execution&
&/executions&通过在配置文件中使用 Include 与
Exclude,可以显式地指定哪些包和类被列入单元测试的统计范围,哪些包和类被剔除在此范围之外。正则表达式支持丰富的匹配条件,可以满足大多数项目对单元测试范围的要求。以上代码将
exs.res.process.egencia 下面所有的名称 Mock 开头的类,以及 exs.res.process.egencia.test 包下面以 Test
结尾的类都剔除在测试范围以外。在使用这种配置之后,代码整体的范围被缩小,因此在被覆盖到的代码数量不变的基础上,整个代码覆盖率会较以前提高。输出结果如图 2
所示。图 2. 包、类过滤效果过滤类中的函数最新版本中的 Cobertura 只能支持到类级别的过滤,而对于类中方法的过滤是不支持的。因此我们需要通过修改 Cobertura 源码,使 Cobertura
支持对类中方法的过滤。对 Cobertura 及其插件改动所依据的主要原理是 : 修改 Cobertura-maven-plugin 项目中的 InstrumentationTask 类,增加
Ignoretrival,IgnoreMethod 等新增 POM 参数。配制正则表达式,修改 Cobertura 核心,在标注(instrumentation)
阶段遍历函数名时,检测函数名是否匹配传入的正则表达式,过滤函数体代码,从而把这些函数代码排除在代码覆盖统计之外,节省开发人员对这类代码的测试精力。清单 4 至清单 6 是对 Cobertura 的几处核心改动,仅供读者参考。清单 4. 对 Cobertura 核心代码的改动之一 private void checkForTrivialSignature() {
Type[] args = Type.getArgumentTypes(myDescriptor);
Type ret = Type.getReturnType(myDescriptor);
if (myName.equals("&init&")) {
isInit = mightBeTrivial =
if (myName.startsWith("set") && args.length == 1 && ret.equals(Type.VOID_TYPE)) {
isSetter =
mightBeTrivial =
if ((myName.startsWith("get") || myName.startsWith("is") || myName.startsWith("has"))
&& args.length == 0 && !ret.equals(Type.VOID_TYPE)) {
isGetter =
mightBeTrivial =
}清单 5. 对 Cobertura Maven plugin 的改动private String ignoreMethodA
private String ignoreT
* 创建一个新的对象,用于进行配置。
public ConfigInstrumentation()
* * 该方法用于设置annotation的名字以用于过滤类内部的方法
* @param ignoreMethodAnnotation
public void setIgnoreMethodAnnotation(String ignoreMethodAnnotation) {
this.ignoreMethodAnnotation = ignoreMethodA
public String getIgnoreTrivial() {
return ignoreT
* 该方法用于标识测试类中的方法是否无关紧要不需要测试。
* @param ignoreTrivial
public void setIgnoreTrivial(String ignoreTrivial) {
this.ignoreTrivial = ignoreT
}清单 6. POM 文件中使用修改后的 Cobertura 过滤类中的方法&reporting&
&groupId&org.codehaus.mojo&/groupId&
&artifactId&cobertura-maven-plugin&/artifactId&
&version&2.5.2&/version&
&configuration&
&!--经过修改的 cobertura, 支持方法级别的过滤 --&
&ignore&*main*&/ignore&
&!--以上修改指的是过滤项目中所有类中的方法名中含有 main 的方法 --&
&/ignores&
&IgnoreTrival&true&/IgnoreTrival&
&/configuration&
&/plugins&
&/reporting&以上修改都完成之后, 就可以运行“mvn:site”命令得到报告。图 4 是使用没有被修改的 Cobertura 产生的结果报告,无函数过滤效果。图 5 是使用被修改后的
Cobertura 产生的结果报告,可以从中看出,几个 set 与 get 方法已被排除在统计范围之外。图 4. 无函数名过滤效果图 5. 增加函数过滤效果利用 Java 反射(Reflection)
机制提高代码的行覆盖率不同的人对反射有不同的理解,大部分人认同的一种观点是:反射使得程序可以检查自身结构以及软件环境,并且根据程序检测到的实际情况改变行为。为了实现自检,一段程序需要有些信息来表示自身,这些信息就称为元数据(metadata)。Java
运行过程中对这些元数据的自检称为内省(introspection)。内省过程之后往往进行行为改变。总的来说,反射 API 利用以下三种技术来实现行为改变: 直接修改元数据。 利用元数据进行操作。 调解(Intercession), 代码被允许在程序各种运行期进行调整。Java 语言反射机制提供一组丰富的 API 函数来操作元数据,且提供了少部分重要的 API 来实现 Intercession 能力。实际项目中,为了保证软件代码的整体质量,单元测试不仅要覆盖类的公有成员,还要覆盖重要的私有成员。而有些私有成员的调用,会被放入到极为复杂的条件分支中。而构造进入这个私有方法的相关条件,可能需要开发人员编写大量测试代码及测试数据。这无疑增加了单元测试的成本。有时为了节省成本,该类私有方法便跳过不测,从而在无形中降低了代码的行覆盖率,影响了软件的整体质量。而利用反射的一系列特性,我们可以在不改变源代码的情况下,直接对复杂的私有方法进行单元测试,无需增加行覆盖检查中被覆盖的代码行数,从而可以在不增加单元测试成本的前提下,提高代码的行覆盖率与单元测试的整体质量。清单 7 给出了一段简单的目标测试代码示例。清单 7. 目标测试代码示例package exs.res.
public class Customer{
private String sayHello()
return "Hello";
public String pHello()
return "pHello";
}为了测试私有函数 sayHello(),利用反射元数据操作 API 的测试代码为:清单 8. 利用反射元数据操作 API 的测试代码
public void privateMethodTest() {
final Method methods[] = Customer.class.getDeclaredMethods();
for (int i = 0; i & methods. ++i) {
if ("sayHello".equals(methods[i].getName())) {
//这里会将 sayHello 方法由 private 变为 public,从而可以直接被外部对象访问
methods[i].setAccessible(true);
String anotherString =(String)methods[i].invoke(new Customer(), new Object[0]);
assertTrue("Hello".equalsIgnoreCase(anotherString));
}catch(Exception e){
e.printStackTrace();
public void privateFieldTest()
throws NoSuchFieldException, SecurityException{
Field message = Customer.class.getDeclaredField("message");
Customer testCustomer = new Customer();
//这里会将 message 属性由 private 变为 public,从而可以直接被外部对象访问
message.setAccessible(true);
message.set(testCustomer, "newMessage");
assertTrue("newMessage".equalsIgnoreCase((String)message.get(testCustomer)));
}catch(Exception e){
e.printStackTrace();
}运行以上单元测试用例来分别对 Customer 的私有方法 sayHello 以及私有属性 message 进行直接访问,结果如图 6 所示。图 6. 非公有函数测试效果从图中我们可以看到 Customer 成员的私有方法 sayHello
被测试代码覆盖到。所以,当一些代码函数复杂度过高,到通过构造测试数据或测试用例的方法很难使非公有成员得到运行时,我们就可以利用 Java
反射机制,直接在测试类中调用和测试目标类的非公有成员,从而提高覆盖率。结语本文使用两种方法,从两个不同的角度对单元测试中的代码覆盖率进行了增强。改进 Cobertura
来提高单元测试代码覆盖率,主要从缩小参与测试的代码总范围的角度入手,适用于代码总数庞大而被测代码数量不多的情况。而使用 Java
反射机制提高单元测试代码覆盖率,主要从提高被测代码数量的角度入手,适用于被测代码私有成员多且触发条件苛刻的情况。针对项目中对单元测试的不同需求,选取合适的技术来增强单元测试,才能真正提高代码以至项目的总体质量。
参考 。查看对初学者很有帮助的示例以及相关资源的链接。
下载 Cobertura。
从 下载源码及教程,了解更多此工具插件的基本原理。
中的相关文章,了解更多相关插件的基本使用方法,以及主要配制项的参数设置示例。
查看文章“”,了解使用 Cobertura 进行代码代码测试覆盖率统计的基本方法和经验。
查看文章“”,了解使用 JUnit 框架编写单元测试的基本过程和方法。 :这里有数百篇关于 Java
编程各个方面的文章。 加入 。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
developerWorks: 登录
标有星(*)号的字段是必填字段。
保持登录。
单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件。
在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。
所有提交的信息确保安全。
选择您的昵称
当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。昵称长度在 3 至 31 个字符之间。
您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。
标有星(*)号的字段是必填字段。
(昵称长度在 3 至 31 个字符之间)
单击提交则表示您同意developerWorks 的条款和条件。 .
所有提交的信息确保安全。
文章、教程、演示,帮助您构建、部署和管理云应用。
立即加入来自 IBM 的专业 IT 社交网络。
为灾难恢复构建应用,赢取现金大奖。
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=Java technologyArticleID=948632ArticleTitle=使用 Cobertura 和反射机制提高 Java 单元测试中的代码覆盖率publish-date=}

我要回帖

更多关于 友元函数访问私有成员 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信