收藏 分享(赏)

追求代码质量-不要被覆盖报告所迷惑.doc

上传人:cjc2202537 文档编号:121981 上传时间:2018-03-21 格式:DOC 页数:14 大小:270KB
下载 相关 举报
追求代码质量-不要被覆盖报告所迷惑.doc_第1页
第1页 / 共14页
追求代码质量-不要被覆盖报告所迷惑.doc_第2页
第2页 / 共14页
追求代码质量-不要被覆盖报告所迷惑.doc_第3页
第3页 / 共14页
追求代码质量-不要被覆盖报告所迷惑.doc_第4页
第4页 / 共14页
追求代码质量-不要被覆盖报告所迷惑.doc_第5页
第5页 / 共14页
点击查看更多>>
资源描述

1、追求代码质量: 不要被覆盖报告所迷惑您是否曾被测试覆盖度量引入歧途?测试覆盖工具对单元测试具有重要的意义,但是经常被误用。这个月,Andrew Glover 会在他的新系列 追求代码质量 中向您介绍值得参考的专家意见。第一部分深入地介绍覆盖报告中数字的真实含义。然后他会提出您可以尽早并经常地利用覆盖来确保代码质量的三个方法。您还记得以前大多数开发人员是如何追求代码质量的吗。在那时,有技巧地放置 main() 方法被视为灵活且适当的测试方法。经历了漫长的道路以后,现在自动测试已经成为高质量代码开发的基本保证,对此我很感谢。但是这还不是我所要感谢的全部。Java 开发人员现在拥有很多通过代码度量、

2、静态分析等方法来度量代码质量的工具。我们甚至已经设法将重构分类成一系列便利的模式!所有的这些新的工具使得确保代码质量比以前简单得多,不过您还需要知道如何使用它们。在这个系列中,我将重点阐述有关保证代码质量的一些有时看上去有点神秘的东西。除了带您一起熟悉有关代码质量保证的众多工具和技术之外,我还将为您说明: 定义并有效度量最影响质量的代码方面。 设定质量保证目标并照此规划您的开发过程。 确定哪个代码质量工具和技术可以满足您的需要。 实现最佳实践(清除不好的),使确保代码质量及早并经常地成为开发实践中轻松且有效的方面。在这个月,我将首先看看 Java 开发人员中最流行也是最容易的质量保证工具包:测

3、试覆盖度量。谨防上当这是一个晚上鏖战后的早晨,大家都站在饮水机边上。开发人员和管理人员们了解到一些经过良好测试的类可以达到超过 90% 的覆盖率,正在高兴地互换着 NFL 风格的点心。团队的集体信心空前高涨。从远处可以听到 “放任地重构吧 ” 的声音,似乎缺陷已成为遥远的记忆,响应性也已微不足道。但是一个很小的反对声在说:女士们,先生们,不要被覆盖报告所愚弄 。现在,不要误解我的意思:并不是说使用测试覆盖工具是愚蠢的。对单元测试范例,它是很重要的。不过更重要的是您如何理解所得到的信息。许多开发团队会在这儿犯第一个错。高覆盖率只是表示执行了很多的代码,并不意味着这些代码被 很好地 执行。如果您关

4、注的是代码的质量,就必须精确地理解测试覆盖工具能做什么,不能做什么。然后您才能知道如何使用这些工具去获取有用的信息。而不是像许多开发人员那样,只是满足于高覆盖率。测试覆盖度量测试覆盖工具通常可以很容易地添加到确定的单元测试过程中,而且结果可靠。下载一个可用的工具,对您的 Ant 和 Maven 构建脚本作一些小的改动,您和您的同事就有了在饮水机边上谈论的一种新报告:测试覆盖报告 。当 foo 和 bar 这样的程序包令人惊奇地显示 高 覆盖率时,您可以得到不小的安慰。如果您相信至少您的部分代码可以保证是 “没有 BUG” 的,您会觉得很安心。但是这样做是一个错误。存在不同类型的覆盖度量,但是绝

5、大多数的工具会关注 行覆盖 ,也叫做 语句覆盖 。此外,有些工具会报告 分支覆盖 。通过用一个测试工具执行代码库并捕获整个测试过程中与被 “触及” 的代码对应的数据,就可以获得测试覆盖度量。然后这些数据被合成为覆盖报告。在 Java 世界中,这个测试工具通常是 JUnit 以及名为 Cobertura、Emma 或 Clover 等的覆盖工具。行覆盖 只是指出代码的哪些行被执行。如果一个方法有 10 行代码,其中的 8 行在测试中被执行,那么这个方法的行覆盖率是 80%。这个过程在总体层次上也工作得很好:如果一个类有 100 行代码,其中的 45 行被触及,那么这个类的行覆盖率就是 45%。同

6、样,如果一个代码库包含 10000 个非注释性的代码行,在特定的测试运行中有 3500 行被执行,那么这段代码的行覆盖率就是 35%。报告 分支覆盖 的工具试图度量决策点(比如包含逻辑 AND 或 OR 的条件块)的覆盖率。与行覆盖一样,如果在特定方法中有两个分支,并且两个分支在测试中都被覆盖,那么您可以说这个方法有 100% 的分支覆盖率。问题是,这些度量有什么用?很明显,很容易获得所有这些信息,不过您需要知道如何使用它们。一些例子可以阐明我的观点。代码覆盖在活动我在清单 1 中创建了一个简单的类以具体表述类层次的概念。一个给定的类可以有一连串的父类,例如 Vector,它的父类是 Abst

7、ractList,AbstractList 的父类又是 AbstractCollection,AbstractCollection 的父类又是 Object:清单 1. 表现类层次的类package com.vanward.adana.hierarchy;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class Hierarchy private Collection classes;private Class baseClass;public Hierarchy(

8、) super();this.classes = new ArrayList();public void addClass(final Class clzz)this.classes.add(clzz);/* return an array of class names as Strings*/public String getHierarchyClassNames()final String names = new Stringthis.classes.size(); int x = 0;for(Iterator iter = this.classes.iterator(); iter.ha

9、sNext();)Class clzz = (Class)iter.next();namesx+ = clzz.getName(); return names;public Class getBaseClass() return baseClass;public void setBaseClass(final Class baseClass) this.baseClass = baseClass;正如您看到的,清单 1 中的 Hierarchy 类具有一个 baseClass 实例以及它的父类的集合。清单 2 中的 HierarchyBuilder 通过两个复制 buildHierarchy

10、的重载的 static 方法创建了 Hierarchy 类。清单 2. 类层次生成器package com.vanward.adana.hierarchy;public class HierarchyBuilder private HierarchyBuilder() super();public static Hierarchy buildHierarchy(final String clzzName) throws ClassNotFoundExceptionfinal Class clzz = Class.forName(clzzName, false, HierarchyBuilder

11、.class.getClassLoader(); return buildHierarchy(clzz);public static Hierarchy buildHierarchy(Class clzz)if(clzz = null)throw new RuntimeException(“Class parameter can not be null“);final Hierarchy hier = new Hierarchy();hier.setBaseClass(clzz);final Class superclass = clzz.getSuperclass();if(supercla

12、ss != null else while(clzz.getSuperclass() != null) hier.addClass(clzz); return hier; 现在是测试时间!有关测试覆盖的文章怎么能缺少测试案例呢?在清单 3 中,我定义了一个简单的有三个测试案例的 JUnit 测试类,它将试图执行 Hierarchy 类和 HierarchyBuilder 类:清单 3. 测试 HierarchyBuilder!package .vanward.adana.hierarchy;import com.vanward.adana.hierarchy.Hierarchy;import

13、com.vanward.adana.hierarchy.HierarchyBuilder;import junit.framework.TestCase;public class HierarchyBuilderTest extends TestCase public void testBuildHierarchyValueNotNull() Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTest.class);assertNotNull(“object was null“, hier);public void

14、 testBuildHierarchyName() Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTest.class);assertEquals(“should be junit.framework.Assert“, “junit.framework.Assert“, hier.getHierarchyClassNames()1); public void testBuildHierarchyNameAgain() Hierarchy hier = HierarchyBuilder.buildHierarch

15、y(HierarchyBuilderTest.class);assertEquals(“should be junit.framework.TestCase“, “junit.framework.TestCase“, hier.getHierarchyClassNames()0); 因为我是一个狂热的测试人员,我自然希望运行一些覆盖测试。对于 Java 开发人员可用的代码覆盖工具中,我比较喜欢用 Cobertura,因为它的报告很友好。而且, Corbertura 是开放源码项目,它派生出了 JCoverage 项目的前身。Cobertura 的报告运行 Cobertura 这样的工具和运行您

16、的 JUnit 测试一样简单,只是有一个用专门逻辑在测试时检查代码以报告覆盖率的中间步骤(这都是通过工具的 Ant 任务或 Maven 的目标完成的)。正如您在图 1 中看到的,HierarchyBuilder 的覆盖报告说明部分代码 没有 被执行。事实上,Cobertura 认为 HierarchyBuilder 的行覆盖率为 59%,分支覆盖率为 75%。图 1. Cobertura 的报告这样看来,我的第一次覆盖测试是失败的。首先,带有 String 参数的 buildHierarchy() 方法根本没有被测试。其次,另一个 buildHierarchy() 方法中的两个条件都没有被执行

17、。有趣的是,所要关注的正是第二个没有被执行的 if 块。因为我所需要做的只是增加一些测试案例,所以我并不担心这一点。一旦我到达了所关注的区域,我就可以很好地完成工作。注意我这儿的逻辑:我使用测试报告来了解什么 没有 被测试。现在我已经可以选择使用这些数据来增强测试或者继续工作。在本例中,我准备增强我的测试,因为我还有一些重要的区域未覆盖。Cobertura:第二轮清单 4 是一个更新过的 JUnit 测试案例,增加了一些附加测试案例,以试图完全执行 HierarchyBuilder: 清单 4. 更新过的 JUnit 测试案例package .vanward.adana.hierarchy;i

18、mport com.vanward.adana.hierarchy.Hierarchy;import com.vanward.adana.hierarchy.HierarchyBuilder;import junit.framework.TestCase;public class HierarchyBuilderTest extends TestCase public void testBuildHierarchyValueNotNull() Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTest.class)

19、;assertNotNull(“object was null“, hier);public void testBuildHierarchyName() Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTest.class);assertEquals(“should be junit.framework.Assert“, “junit.framework.Assert“, hier.getHierarchyClassNames()1); public void testBuildHierarchyNameAgai

20、n() zo Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTest.class);assertEquals(“should be junit.framework.TestCase“, “junit.framework.TestCase“, hier.getHierarchyClassNames()0); public void testBuildHierarchySize() Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTe

21、st.class);assertEquals(“should be 2“, 2, hier.getHierarchyClassNames().length);public void testBuildHierarchyStrNotNull() throws ExceptionHierarchy hier = HierarchyBuilder.buildHierarchy(“.vanward.adana.hierarchy.HierarchyBuilderTest“);assertNotNull(“object was null“, hier);public void testBuildHier

22、archyStrName() throws Exception Hierarchy hier = HierarchyBuilder.buildHierarchy(“.vanward.adana.hierarchy.HierarchyBuilderTest“);assertEquals(“should be junit.framework.Assert“, “junit.framework.Assert“,hier.getHierarchyClassNames()1);public void testBuildHierarchyStrNameAgain() throws ExceptionHie

23、rarchy hier = HierarchyBuilder.buildHierarchy(“.vanward.adana.hierarchy.HierarchyBuilderTest“);assertEquals(“should be junit.framework.TestCase“, “junit.framework.TestCase“,hier.getHierarchyClassNames()0); public void testBuildHierarchyStrSize() throws Exception Hierarchy hier = HierarchyBuilder.bui

24、ldHierarchy(“.vanward.adana.hierarchy.HierarchyBuilderTest“);assertEquals(“should be 2“, 2, hier.getHierarchyClassNames().length); public void testBuildHierarchyWithNull() tryClass clzz = null;HierarchyBuilder.buildHierarchy(clzz);fail(“RuntimeException not thrown“);catch(RuntimeException e)当我使用新的测试

25、案例再次执行测试覆盖过程时,我得到了如图 2 所示的更加完整的报告。现在,我覆盖了未测试的 buildHierarchy() 方法,也处理了另一个 buildHierarchy() 方法中的两个 if 块。然而,因为 HierarchyBuilder 的构造器是 private 类型的,所以我不能通过我的测试类测试它(我也不关心)。因此,我的行覆盖率仍然只有 88%。图 2. 谁说没有第二次机会正如您看到的,使用一个代码覆盖工具 可以 揭露重要的没有相应测试案例的代码。重要的事情是,在阅读报告( 特别 是覆盖率高的)时需要小心,它们也许隐含危险的信息。让我们看看两个例子,看看在高覆盖率后面隐藏

26、着什么。条件带来的麻烦正如您已经知道的,代码中的许多变量可能有多种状态;此外,条件的存在使得执行有多条路径。在留意这些问题之后,我将在清单 5 中定义一个极其简单只有一个方法的类:清单 5.您能看出下面的缺陷吗? package com.vanward.coverage.example01;public class PathCoverage public String pathExample(boolean condition)String value = null;if(condition)value = “ “ + condition + “ “;return value.trim();您

27、是否发现了清单 5 中有一个隐藏的缺陷呢?如果没有,不要担心,我会在清单 6 中写一个测试案例来执行 pathExample() 方法并确保它正确地工作: 清单 6. JUnit 来救援! package .vanward.coverage.example01;import junit.framework.TestCase;import com.vanward.coverage.example01.PathCoverage;public class PathCoverageTest extends TestCase public final void testPathExample() Pat

28、hCoverage clzzUnderTst = new PathCoverage();String value = clzzUnderTst.pathExample(true);assertEquals(“should be true“, “true“, value);我的测试案例正确运行,我的神奇的代码覆盖报告(如下面图 3 所示)使我看上去像个超级明星,测试覆盖率达到了 100%!图 3. 覆盖率明星 我想现在应该到饮水机边上去说了,但是等等,我不是怀疑代码中有什么缺陷呢?认真检查清单 5 会发现,如果 condition 为 false,那么第 13 行确实会抛出 NullPointe

29、rException。Yeesh,这儿发生了什么?这表明行覆盖的确不能很好地指示测试的有效性。路径的恐怖在清单 7 中,我定义了另一个包含 indirect 的简单例子,它仍然有不能容忍的缺陷。请注意 branchIt() 方法中 if 条件的后半部分。(HiddenObject 类将在清单 8 中定义。)清单 7. 这个代码足够简单package com.vanward.coverage.example02;import com.acme.someotherpackage.HiddenObject;public class AnotherBranchCoverage public void branchIt(int value)if(value 100) | (HiddenObject.doWork() = 0)this.dontDoIt();elsethis.doIt(); private void dontDoIt()/dont do something.private void doIt()/do something!

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 研究报告 > 信息产业

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报