posts - 11, comments - 3, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2006年8月10日

http://www.javaresource.org/clearcase/clearcase-73883.html

我这都是转的一些东西

posted @ 2006-08-10 11:18 eddy liao 阅读(264) | 评论 (1)编辑 收藏

2006年8月9日

http://www.chinaitpower.com/2005September/2005-09-13/205598.html

Rational ClearCase是一个业界领先的软件配置管理工具,Rational ClearQuest则是IBM Rational在变更管理和缺陷跟踪方面的软件。业界对于变更管理软件和配置管理软件的集成有着强烈的需求,因此IBM Rational也提供了ClearCase和ClearQuest集成的功能

1 概述

Rational ClearCase是一个业界领先的软件配置管理工具,Rational ClearQuest则是IBM Rational在变更管理和缺陷跟踪方面的软件。业界对于变更管理软件和配置管理软件的集成有着强烈的需求,因此IBM Rational也提供了ClearCase和ClearQuest集成的功能。

所谓Base ClearCase和ClearQuest的集成,就是将ClearQuest中的变更请求(Change Requeset)关联到一个或多个ClearCase中元素(Element)的版本(Version)上。一个变更请求可以被关联到一个或多个版本上,实施变更的这些版本的集合被称作变更请求的变更集(Change Set)。一个版本可以被关联到一个或多个变更请求,这些变更请求的集合被称作版本的请求集(Request Set)。

集成对于不同的角色,有以下不同的功能:

一个项目经理指定在什么情况下需要让用户关联版本到变更请求。也可以指定关联变更请求的VOBs,branches,以及element types。

ClearQuest的管理员添加ClearCase的定义到ClearQuest的schema中。这使得变更请求可以显示与它关联的变更集。

使用ClearCase进行开发的人员,可以在Check Out或者Check In一个版本的时候,将这个版本关联到一个或者更多的变更请求上。也可以查看一个变更请求的变更集。

在这篇文章中,我们将对Base ClearCase与ClearQuest集成的设计原理和运行环境的搭建与设置进行介绍,最后再提供一些操作范例。


2 基本概念

2.1 集中方式(Central Server)

所谓的Central Server就是将所有的脚本文件及配置文件放在一个目录,当进行集成的时候,ClearCase就会在这个目录中寻找配置文件(config.pl)、cqcc_launch脚本以及其他的代码,而不是使用本地默认目录的相应文件,因此提高了安全性和可维护性。与之对应的本地方式(Local Server)则是使用本地ClearCase目录中的配置文件、脚本以及其他代码。

2.2 批处理(Batching Enabled)

就是将一个ClearCase操作中的所有与ClearQuest相关的操作,记录到一个批处理文件中,ClearCase操作完成之后,再将这些操作一次性写入到ClearQuest中。从而降低了登陆ClearQuest和在查询ClearQuest的次数,大大的提高了性能。

2.3 序列(Batching Series)

批处理序列是将批处理的概念进一步扩展的产物。ClearCase认为所有进行的ClearCase都是在一个批处理当中,它记录所有与ClearQuest相关的操作到批处理文件当中,以便在以后的某个时间完成与ClearQuest相关的操作。

2.4 检入后提交(Postcheckin commit)

就是在ClearCase的Check in完成之后,再进行ClearQuest的操作。一般的情况下,在ClearCase的Check in操作完成之后,才进行与ClearQuest相关的操作。这样在Check in操作失败的情况下,会造成ClearCase和ClearQuest的数据不一致。启用此功能则可以避免这种错误。

2.5 自动关联(Auto-association)

就是在将变更请求关联到某个版本的时候,不需要手工选择,而是靠预先设置的请求ID或者根据ClearCase操作的注释自动提取请求ID,来决定关联的请求。

2.6 使用CQWeb方式的集成

在本地没有安装ClearQuest,或者不愿意使用本地的ClearQuest的情况下,可以使用CQWeb的方式使用CQWeb Server上的ClearQuest来实现ClearCase和ClearQuest的集成。


3 何时采用Base ClearCase

我们知道UCM是一种对版本控制的配制管理流程,而UCM是基于Base ClearCase的管理流程演变而来的。因此掌握并了解Base ClearCase的管理就显得至关重要。Base ClearCase包含了一系列功能,它们能够使开发人员做到并行开发,项目管理者也能通过制定相关的规则来使开发工作有序的进行。

在开发过程中,Base ClearCase应用"分支(Branch)"的方法来允许开发人员进行并行开发。任何在配制管理下的元素(Element),例如:文本文件,程序原代码等,都会生成一个主分支,而主分支下还可以有多个下属分支,它们的作用是用来支持在主分支上的开发。Base ClearCase 允许创建复杂的分支体系。在开发过程中,通过视图(View)可以访问特定元素集的特定版本,而这通过修改视图的规则(Config Specification)就可以实现。UCM也使用"分支"的方法,但是这些分支不需要用手工来操作,而是通过"流(Stream)"来实现,通常情况下,一个项目存在一个集成流和多个开发流。

在项目管理方面,我们通过对项目的源文件打基线(Baseline)来呈现项目早期较稳定版本的雏形,并且基线可以用来连接一系列相关的源文件,比如像源代码,测试计划等等。UCM自动完成基线的创建,而Base ClearCase则通过对元素(Elements)的版本打标签来创建基线。

通过以上对UCM和Base ClearCase的比较,因此在一个项目不是很大,并且业务流程相对简单的情况下适合用Base ClearCase。


4 运行环境的搭建与设置

4.1 运行环境的搭建

在Base CCCQ集成的过程中,运行环境的搭建尤为重要。


图 (01) 系统结构图

首先,需要在ClearCase客户端和ClearCase注册服务器安装ClearCase。在ClearQuest Unix服务器和ClearQuest Windows服务器安装ClearQuest。准备数据库服务器。在ClearQuest Unix服务器上配置好DBSet,并添加User DB。之后就可以配置集成了。

4.2 ClearCase与ClearQuest集成的配制

集成的配置需要在ClearCase和ClearQuest上分别进行配置,才能完成。在ClearCase侧,需要对VOB配置。当对一个VOB配置了集成之后,针对与这个VOB的ClearCase相关操作(例如CheckOut, CheckIn)都会激发脚本对ClearQuest数据库的访问,进而完成Base CC和CQ的集成。

在ClearQuest侧,需要在数据库中添加ClearCase的定义,只有加入了定义之后,数据库中的请求的变更集才能够显示出来。

下面具体介绍配置过程。

4.2.1 将ClearCase package加入到一个ClearQuest DBset

由于ClearQuest schema包含了一些与多个ClearQuest user databases相关联的特性,例如数据记录的类型,区域,和形式。在开发人员将ClearCase中文件的版本与ClearQuest用户数据库中的变更请求相联系的时候,必须将ClearCase的特性也加入到ClearQuest schema,此过程要在Windows端完成且过程如下所述:

  • 开始 -> 程序 -> Rational Software -> Rational ClearQuest -> ClearQuest Designer
  • 在ClearQuest Designer中,点击Package -> Package Wizard
  • 在安装Package向导中,找到ClearCase 1.0和ClearCase Upgrade 1.0,如果这些Packages没有列出,则点击"More Packages",并将上述的两个Packages添加到列表中。
  • 选择ClearCase 1.0 Package并点击"下一步"
  • 选择一个将会应用ClearCase 1.0 Package的schema e.g. Defect Tracking,点击"下一步"
  • 选择数据纪录的类型并点击"完成"
  • 选择File -> Check In来保存schema的最新版本
  • 选择Database -> Upgrade Database把schema的最新版本升级到ClearQuest user database中

4.2.2 在ClearCase VOBs上安装触发器(Triggers)

CCCQ的集成应用到了针对cleartool checkin, checkout和uncheckout操作的触发器,触发器的安装与配制需要在Windows端配制,该Windows的Registry Server必须与UNIX上建VOBs的那台Server指向同一台Registry Server。具体配置过程如下所述:

4.2.2.1 同步UNIX与Windows上的ClearCase Regions

1) 在Windows上新建一个Region,名称与需要同步的UNIX上的Region名称相同,这时UNIX上的Region就在Registry Server上注册了。

2) 运行 -> cleartool -> mkregion -tag <UNIX region>

3) 开始 -> 程序 -> Rational Software ->

4) Rational ClearCase'Administration'Region Synchronizer


图 (02) 导入Unix服务器上的VOB

5) 选择需要同步的Windows Region和UNIX Region, 在Import Type一项上选择"VOB Tags"并且选中"Show full storage directory paths.

6) 在"Unix VOB tags not found in the Windows region"列表中选择需要引入的VOB,点击"Import",这时"Create VOB Tag"对话框会显示出来。在"Global Storage"一项中输入在UNIX服务器上的VOB的网络存储路径,并且在"Hostname"一项中输入在Region内能够解析的主机名。


图 (03) 创建Tag

4.2.2.2 将一个VOB安装上Trigger

当一个VOB被引入(Import)后,我们可以对其安装Trigger 在ClearCase中,点击开始 -> 程序 -> Rational Software'Rational ClearCase'Administration'Integrations'ClearQuest Integration Configuration. 这时出现如下图所示的对话框。


图 (04) 应用Trigger

在"ClearCase - ClearQuest Integration Configuration"对话框中,我们可以看到所有在UNIX服务器端建立好的VOBs,并且可以对其中任何一个VOB安装trigger。在这里,我们对VOB int4安装Checkout和Checkin的trigger。Trigger的配制文件在config.pl中有详细说明,关于trigger选择的详细内容可以参看上一章节。

提示:

  • 触发器使用config.pl配制文件来控制本地集成的配制参数。当选择V2触发器时,配置应用程序会将config.pl文件路径设为CQCC/config.pl,在这个路径中CQCC代表了本地的cc-home-dir/lib/perl5/CQCCTrigger/CQCC这个路径,用户可以根据需要将这个路径改变为一个UNC路径,因此所有的集成操作将调用一个中心配制文件config.pl。
  • 在安装触发器时,只有VOB的所有者才可以对自己创建的VOB安装触发器。如果一个用户e.g. Harry登陆Windows,他想对Andy在UNIX上创建的VOB安装触发器,这时会出现"无法得到触发器类型"等警告。如果Harry希望可以对VOB安装触发器,那么需要执行以下两步:
  • 在DOS模式下运行Runas /user:RATIONALCC\Andy cmd.exe命令,这个命令将以Andy的身份打开一个DOS窗口,并提示输入用户名和密码。
  • 在验证通过登陆后,另一个DOS窗口将会打开,在这个窗口中,运行"cqconfig"来以Andy的身份在VOB上安装触发器。

4.2.3 核心文件config.pl的配置

config.pl文件的配置在Base ClearCase与ClearQuest集成的操作中起到重要的作用。config.pl文件中包含了一系列变量及参数的设置,设置的描述,以及在哪里可以配制这些参数(是在config.pl文件本身中设置还是在系统环境变量中设置)。

config.pl文件在不同操作系统上的存储路径:
Windows:C:\Program Files\Rational\Clearcase\lib\perl5\CQCCTrigger\CQCC\config.pl
UNIX: /usr/atria/sun5/lib/perl/CQCCTrigger/CQCC/config.pl

下面就一些重要的参数配置进行详细的说明:

4.2.3.1 定义用户数据库

&SetConfigParm("CQCC_DATABASE_ENTITY_LIST","SAMPL: defect");
CQCC_DATABASE_ENTITY_LIST参数定义了一个或多个数据库和数据库所支持的数据纪录类型。当定义多个数据库时,参数的使用格式为:dbname1: entity1,entity2; dbname2: entity3,entity4。值得注意的是数据纪录类型必须为在schema中已定义好的内容。

4.2.3.2 定义DBsets

&SetConfigParm("CQCC_DATABASE_SET", "<db_set_name>");
在ClearQuest中,当建立有多个DBsets时,即有多个schema存储空间时,CQCC_DATABASE_SET参数用来指定一个当前可以使用的schema存储空间。

4.2.3.3 选择集成模式: 文本模式或图形模式

&SetConfigParm("CQCC_GUI_ENABLE", "OFF");
此参数是一个开启Perl/TK GUI图形界面的开关。如果设置为"ON"(默认情况下),那么图形界面会在需要的情况下显示,例如,在运行xclearcase时。如果设置为"Always",那么图形界面会在命令行操作的形式也显示。如果设置为"OFF",那么图形界面将永远不显示,因此只可以用命令行操作。

4.2.3.4 开启DEBUG模式

&SetConfigParm("CQCC_DEBUG", "1");

此参数用来控制在运行时模式下DEBUG报告的输出级别。0 - 代表没有输出;1 - 代表基本输出(针对高级别的操作);2 - 代表细节输出。

提示:其他参数设置的详细说明请参看config.pl文件。

4.2.4 执行Base CCCQ集成的最后检验

此时,根据以上所提供的信息,我们应能够完成cqcc检验,检验ClearCase与ClearQuest是否能够很有效的结合,并可以开始完成一些简单的操作。

在UNIX客户端运行:cqcc_launch -test

此时,cqcc_launch命令将会调用config.pl里的参数并试图连接ClearQuest,如果连接成功,exit_status会显示0,否则将显示1(如下图所示)


图 (05) 验证配置


5 在Windows的平台上的操作范例

可以说,Base ClearCase的基本操作,就是Check Out和Check in两个操作,下面就简单介绍一下这两个操作。

5.1 Check Out

1) 在ClearCase Explorer中,选中一个文件,进行Check Out操作。如果是配置完成后第一次进行操作,需要输入ClearQuest的用户名和密码。


图 (06) 登陆窗口

2) 登陆成功后,就会出现QSW(Query Association Window)窗口,显示满足条件的缺陷。选择缺陷,点击Association按钮,可以将其放到上侧窗口中,点击OK,即可完成关联。


图 (07) 关联窗口

3) 关联成功后,在ClearQuest中打开相应的缺陷,在ClearCase页中,可以查看到关联的文件。


图 (08) 在ClearQuest中查询关联的文件

4) 在ClearCase Explorer中右键点击被关联的文件,选择版本属性,查看被关联的缺陷。


图 (09) 在ClearCase中查询关联的问题

5.2 Check In

1) 在ClearCase Explorer中选中文件,进行Check Out操作,弹出QSW窗口。


图 (10) 关联窗口

2) 在ClearQuest中查看被关联的文件。


图 (11) 在ClearQuest中查询关联的文件

3) 在ClearCase中查看被关联的缺陷。


图 (12) 在ClearCase中查询关联的文件

posted @ 2006-08-09 17:04 eddy liao 阅读(298) | 评论 (0)编辑 收藏

2006年7月25日

1.在不同网络环境中ClearCase的管理
http://www-128.ibm.com/developerworks/cn/rational/r-hanss/

2.IBM Rational ClearCase-Samba 协同环境的设置和问题解决
http://www-128.ibm.com/developerworks/cn/rational/r-cc-samba/

3.ClearCase Interoperation实例详解
http://www-128.ibm.com/developerworks/cn/rational/06/r-shixl2/index.html

我也别贴了,到这里去找吧。
http://www-128.ibm.com/developerworks/cn/views/rational/articles.jsp

posted @ 2006-07-25 14:01 eddy liao 阅读(211) | 评论 (0)编辑 收藏

先把找到的参考资料发上来,还不知道有没有用,再整理吧。
http://www.microsoft.com/china/windowsserver2003/default.mspx

Windows Server 2003 部署通用结构分步指南

第一部分:将 Windows Server 2003 安装为域控制器
http://www.microsoft.com/china/technet/prodtechnol/windowsserver2003/technologies/directory/activedirectory/stepbystep/domcntrl.mspx

 

Win2K安装与服务器配置(下)
http://article.pchome.net/00/01/79/90/

Windows Server 2003中的Active Directory服务
http://www.microsoft.com/china/technet/community/columns/profwin/pw0503.mspx

轻松配置Windows2003自带MAIL服务器
http://server.chinabyte.com/91/2394591.shtml
手把手教您架设Windows2003共享服务器
http://server.chinabyte.com/185/2482185.shtml

posted @ 2006-07-25 12:36 eddy liao 阅读(9037) | 评论 (2)编辑 收藏

2006年7月20日

checkstyle 是一个帮助开发者按照某种习惯编写 java 代码的工具,他实现了代码检查的自动化,帮助人们从这种繁琐的工作中解放出来。

默认提供了对 sun 编程规范的支持,但是 checkstyle 是一个具有高可配置性的,你完全可以根据自己的要求来配置需要检查的内容。

有以下这些东西
\lib\checkstyle-3.1\contrib\checkstyle-noframes.xsl
\lib\checkstyle-3.1\checkstyle-all-3.1.jar
\lib\checkstyle-3.1\sun_checks.xml                    

在build.xml文件中添加
    <patternset id="java.files.pattern" includes="**/*.java"/>

    <target name="checkstyle" depends="prepare"           依赖prepare target
        description="Check code style for compliance with coding standards">
        <property name="checkstyle.data.dir"
            location="${build.dir}/docs/checkstyle"/>                 存放路径
        <property name="checkstyle.data.file"
            location="${checkstyle.data.dir}/checkstyle.xml"/>     xml文件
        <property name="checkstyle.report.file"
            location="${checkstyle.data.dir}/checkstyle.html"/>   html文件
        <property name="checkstyle.xsl.file"
            location="${checkstyle.dir}/contrib/checkstyle-noframes.xsl"/>   选用的样式表,checkstyle.dir为jar包的位置
        <mkdir dir="${checkstyle.data.dir}"/>
        <taskdef resource="checkstyletask.properties" classpath="${checkstyle.jar}"/>  引入jar文件
        <checkstyle config="${checkstyle.dir}/sun_checks.xml"                      选用sun的规范,可以修改为自己的最佳实践
            failOnViolation="false" failureProperty="checkstyle.failure">
            <fileset dir="src">                                                                   对src目录进行检查
                <patternset refid="java.files.pattern"/>
            </fileset>
            <fileset dir="test">                                                                  对test目录进行检查
                <patternset refid="java.files.pattern"/>
            </fileset>
            <!-- uncomment to print to console as well -->
            <!--formatter type="plain"/-->
            <formatter type="xml" toFile="${checkstyle.data.file}"/>         生成xml文件
        </checkstyle>
        <xslt in="${checkstyle.data.file}" out="${checkstyle.report.file}"
            style="${checkstyle.xsl.file}"/>                                        生成报告,其格式取决于checkstyle.xsl.file
    </target>

如图所示:图1列出了所有文件,图2列出了所以错误

checkstyle1.JPG


checkstyle2.JPG



下面解释了一些常见的输出结果,以供参考。  
序号            输出内容意义    
1  Type  is  missing  a  javadoc  commentClass    缺少类型说明    
2“{”  should  be  on  the  previous  line  “{”  应该位于前一行    
3Methos  is  missing  a  javadoc  comment方法前面缺少javadoc注释    
4Expected  @throws  tag  for  “Exception”在注释中希望有@throws的说明    
5“.”  Is  preceeded  with  whitespace  “.”  前面不能有空格    
6“.”  Is  followed  by  whitespace“.”  后面不能有空格    
7“=”  is  not  preceeded  with  whitespace“=”  前面缺少空格    
8“=”  is  not  followed  with  whitespace“=”  后面缺少空格    
9“}”  should  be  on  the  same  line“}”  应该与下条语句位于同一行    
10Unused  @param  tag  for  “unused”没有参数“unused”,不需注释    
11Variable  “CA”  missing  javadoc变量“CA”缺少javadoc注释    
12Line  longer  than  80characters行长度超过80    
13Line  contains  a  tab  character行含有”tab”  字符    
14Redundant  “Public”  modifier冗余的“public”  modifier    
15Final  modifier  out  of  order  with  the  JSL  suggestionFinal  modifier的顺序错误    
16Avoid  using  the  “.*”  form  of  importImport格式避免使用“.*”    
17Redundant  import  from  the  same  package从同一个包中Import内容    
18Unused  import-java.util.listImport进来的java.util.list没有被使用    
19Duplicate  import  to  line  13重复Import同一个内容    
20Import  from  illegal  package从非法包中  Import内容    
21“while”  construct  must  use  “{}”“while”  语句缺少“{}”    
22Variable  “sTest1”  must  be  private  and  have  accessor  method变量“sTest1”应该是private的,并且有调用它的方法    
23Variable  “ABC”  must  match  pattern  “^[a-z][a-zA-Z0-9]*$”变量“ABC”不符合命名规则“^[a-z][a-zA-Z0-9]*$”    
24“(”  is  followed  by  whitespace“(”  后面不能有空格  25“)”  is  proceeded  by  whitespace“)”  前面不能有空格
25 Line has trailing spaces  行的最后不能有空格

根据sun_checks.xml文件的内容,可以到http://checkstyle.sourceforge.net/checks.html这里查看具体的配置,实现你们的最佳实践

posted @ 2006-07-20 15:51 eddy liao 阅读(2757) | 评论 (0)编辑 收藏

2006年7月19日

http://www-128.ibm.com/developerworks/cn/java/j-jester/

用 Jester 对测试进行测试

测试套件有缺陷,这不是玩笑

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


最新推荐

Java 应用开发源动力 - 下载免费软件,快速启动开发


级别: 初级

Elliotte Rusty Harold , 副教授, Polytechnic University

2005 年 6 月 02 日

全面的单元测试套件对健壮的程序是必不可少的。但是如何才能保证测试套件测试了应当测试的每件事呢?Ivan Moore 的 JUnit 测试的测试器 Jester,擅长发现测试套件的问题,并提供对代码基本结构的深入观察。Elliotte Rusty Harold 介绍了 Jester 并展示如何使用它才能得到最佳结果。

测试先行的开发是极限编程(XP)中争议最少、采用最广泛的部分。到目前为止,大多数专业 Java 程序员都可能捕捉过测试 bug。(请参阅 参考资料 获得有关“被测试传染”的更多信息。) JUnit 是 Java 社区事实上的标准测试框架,没有经过全面的 JUnit 测试套件测试过的系统是不完整的。如果您的项目有全面的测试套件,那么恭喜您:您将制作出质量良好的、有利于工作的软件。但是大多数代码基础相当复杂。您能确定每个方法都被测试到、每个分支都进入过么?如果不能,那么当这些方法和分支在生产中执行的时候,应用程序会如何表现呢?

代码覆盖

对代码进行测试的下一步是用 代码覆盖 工具对测试进行度量。代码覆盖是一种查看一套测试覆盖了多少代码的方法。信心的获得,不仅需要知道测试了程序整体,还要知道每个方法在全部可能情况下都得到测试。传统情况下,这类度量的执行方法是在测试执行时对测试进行监视,可以通过 Java 虚拟机调试接口(JVMDI)或 Java 虚拟机工具接口 (JVMTI)进行,或者直接处理字节码。一次都没有执行过的语句是测试不到的。

Clover 和 EMMA(参阅 参考资料) 这类工具采用的这种方法对于发现测试不到的语句很有价值 —— 但是还不够。知道测试套件没有执行某个语句,可以证明该语句没测试到。但是,反过来不成立。如果执行了某一行代码,并不一定代表它得到测试。完全有可能存在这样的情况:测试并没有检查代码行是否生成正确结果。

当然,没有人会编写测试套件对每个语句的结果都进行验证。在众多的问题当中,这个问题可能会破坏封装。您可能认为,针对特定输入,只有方法中的每一行都操作正确,方法才会生成预期结果。但是这个假设并不合理。例如,如果没有测试到所有可能输入,也就没有测试到为处理边际情况而设计的代码,这时会如何呢?有可能还会测试到每行代码,但有可能遗漏真正的 bug。

并不简单

Jester 的方法并不简单。这个工具有可能会报告大量假阳性。例如,它可能把 System.out.println("Copyright 2005 Elliotte Rusty Harold") 语句改成 System.out.println("Copyright 3005 Elliotte Rusty Harold") ,然后报告没有破坏发生。但是,假阳性一般很容易过滤出来。另外,通常也有合适的理由怀疑像这个示例一样的情况是否真的是假阳性。例如,对于版权日期 3005 是否是测试套件应当通知的 bug,有人可能会有异议。





回页首


Jester 简介

这正是 Jester 发挥作用的地方。与 Clover 这类传统的代码覆盖工具不同,Jester 不去查看报告了哪行代码。相反,Jester 会修改源代码、重新编译源代码,然后运行测试套件,查看是否有什么事出错。例如,它会把 1 改成 2,或者把 if (x > y) 改成 if (false)。如果测试套件的关注不够,没有注意到修改,那么就说明遗漏了某项测试。

我将通过在开源的 Jaxen XPath 工具(参阅 参考资料)上应用 Jester 而对它进行演示。Jaxen 有一个基于 JUnit 的测试套件,而且这个套件的代码覆盖并不完善。

入门

在运行 Jester 之前,所有对没有修改的源代码的单元测试都必须测试通过。如果不是这样,那么 Jester 就无法知道是不是它的修改造成了破坏。(为了演示,我不得不修复一个 bug,我过去为它编写了测试用例,但是没有跟踪修复它。)

Jester 与 IDE 的集成不是特别好(或者根本不好),所以要让测试通过,重要的是正确设置 CLASSPATH 和目录。运行测试套件所需要的命令行对于每个项目都是不同的。因为 Jaxen 测试使用指向特定测试文件的相对 URL ,所以它的测试必须在 jaxen 目录中运行。下面是我最后运行 Jaxen 测试的方式:

												
														$ java -classpath ../jester136/jester.jar:target/lib/junit-3.8.1.jar
:target/lib/dom4j-core-1.4-dev-8.jar:target/lib/jdom-b10.jar
:target/lib/xom-1.0d21.jar:target/test-classes:target/classes 
junit.textui.TestRunner org.jaxen.JaxenTests
												
										

在运行 Jester 之前,还需要清楚针对测试套件的一项附加限制。除非测试失败,否则不能打印有关 System.err 的任何内容。Jester 要通过检查打印的内容来判断测试是否成功,所以对 System.err 的程序输出会把 Jester 弄混。

测试套件运行无误之后,请做一份源代码树的拷贝。记住,Jester 要向代码故意加入 bug,所以您可不要冒险在出现问题的情况下遗漏一个 bug。(如果您在使用源代码控制,那么这不会是个大问题。如果没有,请暂停阅读本文,立即把代码签入 CVS 或 Subversion 仓库。)

运行 Jester

要运行 Jester,在路径中必须同时拥有 jester.jar 和 junit.jar(JUnit 没有和 Jester 绑在一起。需要分别下载)。Jester 在类路径中查找它的配置文件,所以必须还要把 Jester 的主目录放在类路径中。当然,还需要添加所测试的应用程序需要的其他 JAR。主类是 jester.TestTester。传递给这个程序的参数是测试应用程序的测试套件名称。(我不得不为 Jaxen 编写一个主类,因为它没有包含一个可以运行它的全部测试的类。)如果把全部必要的 JAR 文件和目录都添加到 CLASSPATH 环境变量,而不是把它们添加到 jre/lib/ext 或者用 -classpath 引用它们,那么 Jester 工作起来会更加稳定。下面是我针对 Jaxen 运行初始测试的方式:

												
														$ export CLASSPATH=src2/java/main:../jester136/jester.jar:../jester136
:target/lib/junit-3.8.1.jar:target/lib/dom4j-core-1.4-dev-8.jar
:target/lib/jdom-b10.jar:target/lib/jdom-b10.jar:target/lib/xom-1.0d21.jar
:target/test-classes:target/classes
$ java  jester.TestTester org.jaxen.JaxenTests src2/java/main 
												
										

Jester 运行很慢,即使检测一个文件也是如此。它显示一个进度对话框,如图 1 所示,并在 System.out 上打印输出,让您知道它在做的工作,并向您保证它并没有完全挂起。


图 1. Jester 进度

如果在第一次运行若干分钟(或者时间足够运行完整的测试套件,甚至更长)之后,什么输出也没有看到,那么 Jester 可能 确实 挂起了,这很可能是因为类路径的问题。如果每件事都进行顺利,那么应当看到像清单 1 所示的输出:


清单 1. Jester 输出
												
																		Use classpath: src2/java/main:../jester136/jester.jar
:../jester136:target/lib/junit-3.8.1.jar:target/lib/dom4j-core-1.4-dev-8.jar
:target/lib/jdom-b10.jar:target/lib/jdom-b10.jar:target/lib/xom-1.0d21.jar
:target/test-classes:target/classes
...
src2/java/main/org/jaxen/BaseXPath.java 
 - changed source on line 192 (char index=7757) from 1 to 2
             answer.size() == ?1 )
        {
            Object first = answ

src2/java/main/org/jaxen/BaseXPath.java 
 - changed source on line 691 (char index=24848) from 0 to 1


        return results.get( ?0 );
    }
}

lots more output...
src2/java/main/org/jaxen/BaseXPath.java 
 - changed source on line 691 (char index=24848) from 0 to 1


        return results.get( ?0 );
    }
}



10 mutations survived out of 11 changes. Score = 10
took 1 minutes
												
										

从清单 1 中可以看到,BaseXPath 没有得到很好的测试。Jester 对类进行了 11 项修改,而只有一项造成测试失败。有些修改是假阳性,但是 11 处修改肯定不应当只报告 1 处。

下一步是在不破坏测试套件的情况下查看 Jester 改变的代码,看看是否需要为它编写测试。Jester 在 GUI 中显示它进行的修改,如 图 1 所示(它不能在无人控制的情况下运行,这有点烦人),在控制台上打印输出,如 清单 1 所示,并生成 XML 文件,文件中是没有产生影响的修改列表,如清单 2 所示:


清单 2. Jester XML 输出
												
																		<JesterReport>
<JestedFile fileName="src2/java/main/org/jaxen/BaseXPath.java" absolutePathFileName=
"/Users/elharo/Documents/articles/jester/jaxen/src2/java/main/org/jaxen/BaseXPath.java" 
numberOfChangesThatDidNotCauseTestsToFail="8" numberOfChanges="11" score="28">
<ChangeThatDidNotCauseTestsToFail index="7691" from="if (" to="if (true ||"/>
<ChangeThatDidNotCauseTestsToFail index="7691" from="if (" to="if (false &&"/>
<ChangeThatDidNotCauseTestsToFail index="7703" from="!=" to="=="/>
<ChangeThatDidNotCauseTestsToFail index="7754" from="==" to="!="/>
<ChangeThatDidNotCauseTestsToFail index="7757" from="1" to="2"/>
<ChangeThatDidNotCauseTestsToFail index="7826" from="if (" to="if (true ||"/>
<ChangeThatDidNotCauseTestsToFail index="7826" from="if (" to="if (false &&"/>
<ChangeThatDidNotCauseTestsToFail index="24749" from="if (" to="if (false &&"/>
</JestedFile></JesterReport>
												
										

Jester 的行号报告通常不是个好方法,所以最好是在控制台输出中查找修改的代码。下面是 清单 1 的报告中的修改:

												
														src2/java/main/org/jaxen/BaseXPath.java 
 - changed source on line 691 (char index=24848) from 0 to 1


        return results.get( ?0 );
    }
}
												
										

在这个方法中,这个修改是在类的结束处:

												
														protected Object selectSingleNodeForContext(Context context) throws JaxenException 
{
  List results = selectNodesForContext( context );

  if ( results.isEmpty() )
  {
    return null;
  }

        return results.get( 0 );
}
												
										

对测试套件迅速查找之后发现,实际上没有测试调用 selectSingleNodeForContext。所以下一步就是为这个方法编写一个测试。这个方法是 protected 的方法,所以测试不能直接调用它。有时需要编写一个子类(通常作为内部类)来测试 protected 的方法。但是在这个例子中,稍做一点检查就很快发现这个方法由同一个类中的两个 public 方法(stringValuenumberValue)直接调用。所以也可以用这两个方法来测试它:

												
														    public void testSelectSingleNodeForContext() throws JaxenException {
        
        BaseXPath xpath = new BaseXPath("1 + 2");
        
        String stringValue = xpath.stringValueOf(xpath);
        assertEquals("3", stringValue);
        
        Number numberValue = xpath.numberValueOf(xpath);
        assertEquals(3, numberValue.doubleValue(), 0.00001);
        
    }
												
										

最后一步是运行测试用例,确定它通过。下面是结果:

												
														java.lang.NullPointerException
	at org.jaxen.function.StringFunction.evaluate(StringFunction.java:121)
	at org.jaxen.BaseXPath.stringValueOf(BaseXPath.java:295)
	at org.jaxen.BaseXPathTest.testSelectSingleNodeForContext(BaseXPathTest.java:23)
												
										

Jester 捕捉到一个 bug!方法没有像预期的那样工作。更有趣的是,对 bug 的调查揭示出潜在的设计缺陷。BaseXPath 类可能更适合作为抽象类而不是具体类。我发誓,我并不是特意挑选这个示例来公开这个 bug。我从 BaseXPath 开始只是因为它是顶级 org.jaxen 包的第一个类,而且我选择 selectSingleNodeForContext 作为所测试的方法也只是因为它是 Jester 报告的最后一个错误。我真的认为这个方法没有什么问题,但是我错了。如果某些事没有经过测试,那么就应当假设它是有问题的。Jester 会告诉您出了什么问题。

下一步显而易见:修复 bug。(请确保同时对 Jester 正在处理的源树拷贝和实际树中的 bug 进行了修复。)然后,迭代 —— 针对这个类重新运行 Jester,直到任何修改都不能通过,或者可以通过的修改都是不相关的。在我为这个 bug 添加测试(并修复)之后,Jester 就报告 11 个修改中只有 8 个没有检测到,如 清单 2 所示。这在调试中是经常出现的事:修复了一个问题就修复(或者暴露了)另外几个。





回页首


Jester 的性能

因为 Jester 重新编译代码基,而且要为自己做的每个修改都重新运行测试套件,所以它的运行要比 Clover 这样的传统工具慢得多。因此,对性能加以关注是很重要的。可以用许多技术加快 Jester 的运行。

首先,如果编译在 Jester 执行时间中占了显著部分,那么请尝试使用一个更快的编译器。许多用户都报告采用 Jikes 代替 Javac 后速度有显著提高(参阅 参考资料)。可以在 Jester 主目录中的 jester.cfg 文件中修改 Jester 使用的编译命令。

第二,剖析和优化测试套件。一般情况下,人们对单元测试运行的速度没太注意,但是如果乘上 Jester 上千次执行测试套件的次数,那么任何节约都会非常显著。具体来说,要在测试套件中查找在正常代码中不会出现的问题。JUnit 会重新初始化每个执行方法的全部字段,所以如果不是测试类的每个方法都用的字段,那么把测试数据从字段中拿出来放在本地变量中,可以显著提高速度。如果形成的代码副本不合您的风格,请尝试把测试套件分成更小、更模块化的类,以便所有的初始数据可以在全部测试方法之间共享。

第三,重新组织测试套件的 suite 方法,以便最脆弱的测试(修改之后最有可能出错的)在不太脆弱的测试之前运行。只要 Jester 发现一个测试失败,就会终止运行,所以尽早失败可以短路大量耗时的额外测试。

第四,出于相似的原因,当测试失败的机会差不多时,把最快的测试放在第一位。按照大概的执行时间给测试排序。只在内存中执行的测试在访问磁盘的测试之前,访问磁盘的测试在访问 LAN 的测试之前,访问 LAN 的测试在访问 Internet 的测试之前。如果有些测试特别慢,试试去掉它们,即便这会增加假阳性的数量。在 XOM (一个用 Java 语言处理 XML 的 API)的测试套件中,在 50 个测试类中,只有很少的几个就占据了 90% 以上的执行时间。在测试的时候清除这些类可以带来 10 倍的性能提升。

最后,也是最重要的,就是不要一次测试整个代码基。每次把测试限制在一个类上,而且只运行能够暴露这个类的覆盖不足的测试。可能需要更长时间来测试每个类,但是用这种方法,几乎可以立即填补不足、修复 bug,而不必为 Jester 的一次运行完成等上好几天。





回页首


结束语

Jester 是聪明的程序员的工具包中一个重要的附加。它可以发现其他工具不能发现的代码覆盖不足,这会直接变成发现和修复 bug。使用 Jester 对代码基进行测试,可以制造出更强壮的软件。





回页首


参考资料





回页首


关于作者

Elliotte Rusty Harold 来自新奥尔良,现在他还定期返回新奥尔良研究一盆秋葵。但是,他和妻子 Beth 及他们的猫咪 Charm(以夸克命名)和 Marjorie(以他的岳母为名),定居在布鲁克林附近的 Prospect Heights。他是 Polytechnic 大学计算机科学的副教授,他在该校讲授 Java 和面向对象编程。他的 Web 站点 Cafe au Lait 已经成为 Internet 上最流行的独立 Java 站点之一,他的分站点 Cafe con Leche 已经成为最流行的 XML 站点之一。他的书包括 Effective XMLProcessing XML with JavaJava Network ProgrammingThe XML 1.1 Bible。他目前在开发处理 XML 的 XOM API 和 XQuisitor GUI 查询工具。

posted @ 2006-07-19 15:33 eddy liao 阅读(254) | 评论 (0)编辑 收藏

http://java-source.net/open-source/code-coverage

Quilt

Quilt is a Java software development tool that measures coverage , the extent to which unit testing exercises the software under test. It is optimized for use with the JUnit unit test package, the Ant Java build facility, and the Maven project management toolkit.

Go To Quilt

EMMA

EMMA is an open-source toolkit for measuring and reporting Java code coverage. EMMA distinguishes itself from other tools by going after a unique feature combination: support for large-scale enterprise software development while keeping individual developer's work fast and iterative at the same time. Every developer on your team can now get code coverage for free and they can get it fast! EMMA is so lightweight developers can use it during the process of writing tests instead of waiting for a "test build". This gets code coverage where it belongs: helping with design and implementation before the code is checked in.

Go To EMMA

NoUnit

NoUnit allows you to see how good your JUnit tests are. It generates a report from your code to graphically show you how many of your project's methods are being tested , and how well.

Go To NoUnit

InsECT

InsECT which stands for Instrumentation Execution Coverage Tool, is a system developed in Java to obtain coverage information for Java programs. InsECT instruments (inserts instructions into) Java class files at the bytecode level with probes to report information about a system at runtime. The goal of InsECT is to provide detailed coverage information about Java programs by taking into full account the object-oriented behavior and language features of Java. Furthermore, as an open-source project, InsECT is designed to be extensible for use in a wide variety of dynamic analyses. InsECT utilizes the Byte Code Engineering Library.

Go To InsECT

Hansel

Hansel is an extension to JUnit that adds code coverage testing to the testing framework.

Go To Hansel

Jester

Jester finds code that is not covered by tests. Jester makes some change to your code, runs your tests, and if the tests pass Jester displays a message saying what it changed. Jester includes a script for generating web pages that show the changes made that did not cause the tests to fail.

Go To Jester

JVMDI Code Coverage Analyser

This small utility is a shared library which when loaded into a Java VM (1.4+) which supports JVMDI will record all the lines of code executed. This is a relatively coarse coverage method, but good enough for a lot of purposes.

Go To JVMDI Code Coverage Analyser

GroboCodeCoverage

GroboCodeCoverage is a 100% Pure Java implementation of a Code Coverage tool. It uses Jakarta's BCEL platform to post-compile class files to add logging statements for tracking coverage.

Go To GroboCodeCoverage

jcoverage/gpl

jcoverage/gpl is a free code-coverage tool for Java聶 programmers that allows them to measure the effectiveness of their Java tests and how much of a software program's code has been tested. jcoverage/gpl identifies how many times each line of code in your application has been executed and you can see which parts of your software remain untested. After instrumenting your code and running your tests, a report is generated allowing you to view information coverage figures from a project level right down to the individual line of code. This process is called 'code coverage'.

Go To jcoverage/gpl

JBlanket

JBlanket is a tool for assessing and improving method coverage of unit test cases. It is integrated with JUnit and Ant.

Go To JBlanket

Cobertura

Cobertura is a free Java tool that calculates the percentage of code accessed by tests. It can be used to identify which parts of your Java program are lacking test coverage. It is based on jcoverage. Cobertura produces very nice reports. It works by instrumenting Java bytecode after it has been compiled.

Go To Cobertura

Coverlipse

An eclipse plugin for code coverage visualization of JUnit Tests. Supported coverages include block coverage and all-uses coverage (Data Flow Analysis). License is CPL (Common Public License)

Go To Coverlipse

posted @ 2006-07-19 15:29 eddy liao 阅读(821) | 评论 (0)编辑 收藏

http://www-128.ibm.com/developerworks/cn/java/j-cq03316/


追求代码质量: 监视圈复杂度

当代码复杂度超出想像时该如何做

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

讨论


最新推荐

Java 应用开发源动力 - 下载免费软件,快速启动开发


级别: 初级

Andrew Glover , 总裁, Stelligent Incorporated

2006 年 4 月 25 日

如果复杂度与缺陷紧密相关,那么监视代码库的复杂度值不是很有意义吗?Andrew Glover 将展示如何使用简单的代码度量工具和基于 Java™ 的工具来监视圈复杂度 (cyclomatic complexity)。

每位开发人员对代码质量的含义都有着自己的看法,并且大多数人对如何查找编写欠佳的代码也有自己的想法。甚至术语代码味道(code smell) 也已进入大众词汇表,成为描述代码需要改进的一种方式。

圈什么?

关于这篇文章和代码质量主题的任何其他文章的问题,请访问由 Andrew Glover 主持的 Improve your Java Code Quality 讨论论坛。

代码味道通常由开发人员直接判定,有趣的是,它是许多代码注释综合在一起的味道。一些人声称公正的代码注释是好事情,而另一些人声称代码注释只是解释过于复杂的代码的一种机制。显然,Javadocs™ 很有用,但是多少内嵌注释才足以维护代码?如果代码已经编写得足够好,它还需要解释自己吗?

这告诉我们,代码味道是一种评估代码的机制,它具有主观性。我相信,那些闻起来味道糟透了的代码可能是其他人曾经编写的最好的代码。以下这些短语听起来是不是很熟悉?

是的,它初看起来有点乱,但是您要看到它多么可扩展!!

或者

它让您感到迷惑,但显然您不了解它的模式。

我们需要的是客观评估代码质量的方法,某种可以决定性地告诉我们正在查看的代码是否存在风险的东西。不管您是否相信,这种东西确实存在!用来客观评估代码质量的机制已经出现了一段时间了,只是大多数开发人员忽略了它们。这些机制被称为代码度量 (code metric)。

代码度量的历史

几十年前,少数几个非常聪明的人开始研究代码,希望定义一个能够与缺陷关联的测量系统。这是一个非常有趣的主张:通过研究带 bug 代码中的模式,他们希望创建正式的模型,然后可以评估这些模型,在缺陷成为缺陷之前 捕获它们。

在这条研究之路上,其他一些非常聪明的人也决定通过研究代码看看他们是否可以测量开发人员的生产效率。对每位开发人员的代码行的经典度量似乎只停留在表面上:

Joe 生产的代码要比 Bill 多,因此 Joe 生产率更高一些,值得我们花钱聘请这样的人。此外,我注意到 Bill 经常在饮水机边闲晃,我认为我们应该解雇 Bill。

但是这种生产率度量在实践中是非常令人失望的,主要是因为它容易被滥用。一些代码测量包括内嵌注释,并且这种度量实际上受益于剪切粘贴式开发 (cut-and-paste style development)。

Joe 编写了许多缺陷!其他每条缺陷也都是由他间接造成的。我们不该解雇 Bill,他的代码实际上是免检的。

可以预见,生产率研究被证实是非常不准确的,但在管理团队 (management body) 广泛使用这种生产率度量以期了解每个人的能力的价值之前,情况并非如此。来自开发人员社区的痛苦反应是有理由的,对于一些人而言,那种痛苦感觉从未真正走远。

未经雕琢的钻石

尽管存在这些失败,但在那些复杂度与缺陷的相互关系的研究中仍然有一些美玉。大多数开发人员忘记进行代码质量研究已有很长一段时间了,但对于那些仍正在钻研的人而言(特别是如果您也正在为追求代码质量而努力钻研),会在今天的应用中发现这些研究的价值。例如,您曾注意到一些长的方法有时难以理解吗?是否曾无法理解嵌套很深的条件从句中的逻辑?您的避开这类代码的本能是正确的。一些长的方法和带有大量路径的方法 难以理解的,有趣的是,这类方法容易导致缺陷。

我将使用一些例子展示我要表达的意思。





回页首


数字的海洋

研究显示,平均每人在其大脑中大约能够处理 7(±2)位数字。这就是为什么大多数人可以很容易地记住电话号码,但却很难记住大于 7 位数字的信用卡号码、发射次序和其他数字序列的原因。

此原理还可以应用于代码的理解上。您以前大概已经看到过类似清单 1 中所示的代码片段:


清单 1. 适用记忆数字的原理
												
														if (entityImplVO != null) {
  List actions = entityImplVO.getEntities();
  if (actions == null) {
     actions = new ArrayList();
  }
  Iterator enItr = actions.iterator();
  while (enItr.hasNext()) {
    entityResultValueObject arVO = (entityResultValueObject) actionItr
     .next();
    Float entityResult = arVO.getActionResultID();
    if (assocPersonEventList.contains(actionResult)) {
      assocPersonFlag = true;
    }
    if (arVL.getByName(
      AppConstants.ENTITY_RESULT_DENIAL_OF_SERVICE)
         .getID().equals(entityResult)) {
      if (actionBasisId.equals(actionImplVO.getActionBasisID())) {
        assocFlag = true;
      }
    }
    if (arVL.getByName(
     AppConstants.ENTITY_RESULT_INVOL_SERVICE)
      .getID().equals(entityResult)) {
     if (!reasonId.equals(arVO.getStatusReasonID())) {
       assocFlag = true;
     }
   }
 }
}else{
  entityImplVO = oldEntityImplVO;
}


												
										

清单 1 展示了 9 条不同的路径。该代码片段实际上是一个 350 多行的方法的一部分,该方法展示了 41 条不同的路径。设想一下,如果您被分配一项任务,要修改此方法以添加一项新功能。如果您该方法不是您编写的,您认为您能只做必要的更改而不会引入任何缺陷吗?

当然,您应该编写一个测试用例,但您会认为该测试用例能将您的特定更改在条件从句的海洋中隔离起来吗?





回页首


测量路径复杂度

圈复杂度 是在我前面提到的那些研究期间开创的,它可以精确地测量路径复杂度。通过利用某一方法路由不同的路径,这一基于整数的度量可适当地描述方法复杂度。实际上,过去几年的各种研究已经确定:圈复杂度(或 CC)大于 10 的方法存在很大的出错风险。因为 CC 通过某一方法来表示路径,这是用来确定某一方法到达 100% 的覆盖率将需要多少测试用例的一个好方法。例如,以下代码(您可能记得本系列的第一篇文章中使用过它)包含一个逻辑缺陷:


清单 2. PathCoverage 有一个缺陷!
												
														public class PathCoverage {
  public String pathExample(boolean condition){
    String value = null;
    if(condition){
      value = " " + condition + " ";
    }
    return value.trim();
  }
}

												
										

作为响应,我可以编写一个测试,它将达到 100% 的行覆盖率:


清单 3. 一个测试产生完全覆盖!
												
														import junit.framework.TestCase;

public class PathCoverageTest extends TestCase {
  public final void testPathExample() {
    PathCoverage clzzUnderTst = new PathCoverage();
    String value = clzzUnderTst.pathExample(true);
    assertEquals("should be true", "true", value);
  }       
}

												
										

接下来,我将运行一个代码覆盖率工具,比如 Cobertura,并将获得如图 1 中所示的报告:


图 1. Cobertura 报告

哦,有点失望。代码覆盖率报告指示 100% 的覆盖率,但我们知道这是一个误导。

二对二

注意,清单 2 中的 pathExample() 方法有一个值为 2 的 CC(一个用于默认路径,一个用于 if 路径)。使用 CC 作为更精确的覆盖率测量尺度意味着第二个测试用例是必需的。在这里,它将是不进入 if 条件语句而采用的路径,如清单 4 中的 testPathExampleFalse() 方法所示:


清单 4. 沿着较少采用的路径向下
												
														import junit.framework.TestCase;

public class PathCoverageTest extends TestCase {
  
  public final void testPathExample() {
    PathCoverage clzzUnderTst = new PathCoverage();
    String value = clzzUnderTst.pathExample(true);
    assertEquals("should be true", "true", value);
  } 

  public final void testPathExampleFalse() {
    PathCoverage clzzUnderTst = new PathCoverage();
    String value = clzzUnderTst.pathExample(false);
    assertEquals("should be false", "false", value);
  } 
}

												
										

正如您可以看到的,运行这个新测试用例会产生一个令人讨厌的 NullPointerException。在这里,有趣的是我们可以使用圈复杂度而不是 使用代码覆盖率来找出这个缺陷。代码覆盖率指示我们已经在一个测试用例之后完成了此操作,但 CC 却会强迫我们编写额外的测试用例。不算太坏,是吧?

幸运的是,这里的测试中的方法有一个值为 2 的 CC。设想一下该缺陷被隐藏在 CC 为 102 的方法中的情况。祝您好运找到它!





回页首


图表上的 CC

Java 开发人员可使用一些开放源码工具来报告圈复杂度。其中一个这样的工具是 JavaNCSS,它通过检查 Java 源文件来确定方法和类的长度。此外,此工具还收集代码库中每个方法的圈复杂度。通过利用 Ant 任务或 Maven 插件配置 JavaNCSS,可以生成一个列出以下内容的 XML 报告:

  • 每个包中的类、方法、非注释代码行和各种注释样式的总数。

  • 每个类中非注释代码行、方法、内部类和 Javadoc 注释的总数。

  • 代码库中每个方法的非注释代码行的总数和圈复杂度。

该工具附带了少量样式表,可以使用它们来生成总结数据的 HTML 报告。例如,图 2 阐述了 Maven 生成的报告:


图 2. Maven 生成的 JavaNCSS 报告

此报告中带有 Top 30 functions containing the most NCSS 标签的部分详细描述了代码库中最长的方法,顺便提一句,该方法几乎总是 与包含最大圈复杂度的方法相关联。例如,该报告列出了 DBInsertQueue 类的 updatePCensus() 方法,因为此方法的非注释行总数为 283,圈复杂度(标记为 CCN)为 114。

正如上面所演示的,圈复杂度是代码复杂度的一个好的指示器;此外,它还是用于开发人员测试的一个极好的衡量器。一个好的经验法则是创建数量与将被测试代码的圈复杂度值相等的测试用例。在图 2 中所见的 updatePCensus() 方法中,将需要 114 个测试用例来达到完全覆盖。





回页首


分而治之

在面对指示高圈复杂度值的报告时,第一个行动是检验所有相应测试的存在。如果存在一些测试,测试的数量是多少?除了极少数代码库以外,几乎所有代码库实际上都有 114 个测试用例用于 updatePCensus() 方法(实际上,为一个方法编写如此多的测试用例可能会花费很长时间)。但即使是很小的一点进步,它也是减少方法中存在缺陷风险的一个伟大开始。

如果没有任何相关的测试用例,显然需要测试该方法。您首先想到的可能是:到重构的时间了,但这样做将打破第一个重构规则,即将编写一个测试用例。先编写测试用例会降低重构中的风险。减少圈复杂度的最有效方式是隔离代码部分,将它们放入新的方法中。这会降低复杂度,使方法更容易管理(因此更容易测试)。当然,随后应该测试那些更小的方法。

在持续集成环境中,随时间变化 评估方法的复杂度是有可能的。如果是第一次运行报告,那么您可以监视方法的复杂度值或任何相关的成长度(growth)。如果在 CC 中看到一个成长度,那么您可以采取适当的动作。

如果某一方法的 CC 值在不断增长,那么您有两个响应选择:

  • 确保相关测试的健康情况仍然表现为减少风险。
  • 评估重构方法减少任何长期维护问题的可能性。

还要注意的是,JavaNCSS 不是惟一用于 Java 平台促进复杂度报告的工具。PMD 是另一个分析 Java 源文件的开源项目,它有一系列的规则,其中之一就是报告圈复杂度。CheckStyle 是另一个具有类似的圈复杂度规则的开放源码项目。PMD 和 CheckStyle 都有 Ant 任务和 Maven 插件(请参阅 参考资料,从那里获得关于至此为止讨论的所有工具的更多信息。)





回页首


使用复杂度度量

因为圈复杂度是如此好的一个代码复杂度指示器,所以测试驱动的开发 (test-driven development) 和低 CC 值之间存在着紧密相关的联系。在编写测试时(注意,我没有暗示是第一次),开发人员通常倾向于编写不太复杂的代码,因为复杂的代码难以测试。如果您发现自己难以编写某一代码,那么这是一种警示,表示正在测试的代码可能很复杂。在这些情况下,TDD 的简短的 “代码、测试、代码、测试” 循环将导致重构,而这将继续驱使非复杂代码的开发。

所以,在使用遗留代码库的情况下,测量圈复杂度特别有价值。此外,它有助于分布式开发团队监视 CC 值,甚至对具有各种技术级别的大型团队也是如此。确定代码库中类方法的 CC 并连续监视这些值将使您的团队在复杂问题出现时 抢先处理它们。





回页首


参考资料

学习

获得产品和技术
  • JavaNCSS :适用于 Java 平台的一个源代码测量套件。

  • PMD :这个流行的开放源码工具扫描 Java 代码以发现问题。

  • CheckStyle :来自 SourceForge 的另一个 Java 分析工具。


讨论




回页首


关于作者

Andrew Glover 是 Stelligent Incorporated 的总裁,该公司采用有效的开发人员测试策略和持续集成技术(让团队能够尽早且经常地监视代码质量)帮助其他公司解决软件开发质量问题。他还是 Java Testing Patterns(Wiley,2004 年 9 月)一书的合著者。

posted @ 2006-07-19 15:06 eddy liao 阅读(291) | 评论 (0)编辑 收藏

http://www-128.ibm.com/developerworks/cn/java/j-cobertura/


用 Cobertura 测量测试覆盖率

找出隐藏 bug 的未测试到的代码

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


最新推荐

Java 应用开发源动力 - 下载免费软件,快速启动开发


级别: 初级

Elliotte Rusty Harold , 副教授, Polytechnic University

2005 年 5 月 26 日

Cobertura 是一种开源工具,它通过检测基本的代码,并观察在测试包运行时执行了哪些代码和没有执行哪些代码,来测量测试覆盖率。除了找出未测试到的代码并发现 bug 外,Cobertura 还可以通过标记无用的、执行不到的代码来优化代码,还可以提供 API 实际操作的内部信息。Elliotte Rusty Harold 将与您分享如何利用代码覆盖率的最佳实践来使用 Cobertura。

尽管测试先行编程(test-first programming)和单元测试已不能算是新概念,但测试驱动的开发仍然是过去 10 年中最重要的编程创新。最好的一些编程人员在过去半个世纪中一直在使用这些技术,不过,只是在最近几年,这些技术才被广泛地视为在时间及成本预算内开发健壮的无缺陷软件的关键所在。但是,测试驱动的开发不能超过测试所能达到的程度。测试改进了代码质量,但这也只是针对实际测试到的那部分代码而言的。您需要有一个工具告诉您程序的哪些部分没有测试到,这样就可以针对这些部分编写测试代码并找出更多 bug。

Mark Doliner 的 Cobertura (cobertura 在西班牙语是覆盖的意思)是完成这项任务的一个免费 GPL 工具。Cobertura 通过用额外的语句记录在执行测试包时,哪些行被测试到、哪些行没有被测试到,通过这种方式来度量字节码,以便对测试进行监视。然后它生成一个 HTML 或者 XML 格式的报告,指出代码中的哪些包、哪些类、哪些方法和哪些行没有测试到。可以针对这些特定的区域编写更多的测试代码,以发现所有隐藏的 bug。

阅读 Cobertura 输出

我们首先查看生成的 Cobertura 输出。图 1 显示了对 Jaxen 测试包运行 Cobertura 生成的报告(请参阅 参考资料)。从该报告中,可以看到从很好(在 org.jaxen.expr.iter 包中几乎是 100%)到极差(在 org.jaxen.dom.html 中完全没有覆盖)的覆盖率结果。


图 1. Jaxen 的包级别覆盖率统计数据

Cobertura 通过被测试的行数和被测试的分支数来计算覆盖率。第一次测试时,两种测试方法之间的差别并不是很重要。Cobertura 还为类计算平均 McCabe 复杂度(请参阅 参考资料)。

可以深入挖掘 HTML 报告,了解特定包或者类的覆盖率。图 2 显示了 org.jaxen.function 包的覆盖率统计。在这个包中,覆盖率的范围从 SumFunction 类的 100% 到 IdFunction 类的仅为 5%。


图 2. org.jaxen.function 包中的代码覆盖率

进一步深入到单独的类中,具体查看哪一行代码没有测试到。图 3 显示了 NameFunction 类中的部分覆盖率。最左边一栏显示行号。后一栏显示了执行测试时这一行被执行的次数。可以看出,第 112 行被执行了 100 次,第 114 行被执行了 28 次。用红色突出显示的那些行则根本没有测试到。这个报告表明,虽然从总体上说该方法被测试到了,但实际上还有许多分支没有测试到。


图 3. NameFunction 类中的代码覆盖率

Cobertura 是 jcoverage 的分支(请参阅 参考资料)。GPL 版本的 jcoverage 已经有一年没有更新过了,并且有一些长期存在的 bug,Cobertura 修复了这些 bug。原来的那些 jcoverage 开发人员不再继续开发开放源码,他们转向开发 jcoverage 的商业版和 jcoverage+,jcoverage+ 是一个从同一代码基础中发展出来的封闭源代码产品。开放源码的奇妙之处在于:一个产品不会因为原开发人员决定让他们的工作获得相应的报酬而消亡。

确认遗漏的测试

利用 Cobertura 报告,可以找出代码中未测试的部分并针对它们编写测试。例如,图 3 显示 Jaxen 需要进行一些测试,运用 name() 函数对文字节点、注释节点、处理指令节点、属性节点和名称空间节点进行测试。

如果有许多未覆盖的代码,像 Cobertura 在这里报告的那样,那么添加所有缺少的测试将会非常耗时,但也是值得的。不一定要一次完成它。您可以从被测试的最少的代码开始,比如那些所有没有覆盖的包。在测试所有的包之后,就可以对每一个显示为没有覆盖的类编写一些测试代码。对所有类进行专门测试后,还要为所有未覆盖的方法编写测试代码。在测试所有方法之后,就可以开始分析对未测试的语句进行测试的必要性。

(几乎)不留下任何未测试的代码

是否有一些可以测试但不应测试的内容?这取决于您问的是谁。在 JUnit FAQ 中,J. B. Rainsberger 写到“一般的看法是:如果 自身 不会出问题,那么它会因为太简单而不会出问题。第一个例子是 getX() 方法。假定 getX() 方法只提供某一实例变量的值。在这种情况下,除非编译器或者解释器出了问题,否则 getX() 是不会出问题的。因此,不用测试 getX(),测试它不会带来任何好处。对于 setX() 方法来说也是如此,不过,如果 setX() 方法确实要进行任何参数验证,或者说确实有副作用,那么还是有必要对其进行测试。”

理论上,对未覆盖的代码编写测试代码不一定就会发现 bug。但在实践中,我从来没有碰到没有发现 bug 的情况。未测试的代码充满了 bug。所做的测试越少,在代码中隐藏的、未发现的 bug 就会越多。

我不同意。我已经记不清在“简单得不会出问题”的代码中发现的 bug 的数量了。确实,一些 getter 和 setter 很简单,不可能出问题。但是我从来就没有办法区分哪些方法是真的简单得不会出错,哪些方法只是看上去如此。编写覆盖像 setter 和 getter 这样简单方法的测试代码并不难。为此所花的少量时间会因为在这些方法中发现未曾预料到的 bug 而得到补偿。

一般来说,开始测量后,达到 90% 的测试覆盖率是很容易的。将覆盖率提高到 95% 或者更高就需要动一下脑筋。例如,可能需要装载不同版本的支持库,以测试没有在所有版本的库中出现的 bug。或者需要重新构建代码,以便测试通常执行不到的部分代码。可以对类进行扩展,让它们的受保护方法变为公共方法,这样就可以对这些方法进行测试。这些技巧看起来像是多此一举,但是它们曾帮助我在一半的时间内发现更多的未发现的 bug。

并不总是可以得到完美的、100% 的代码覆盖率。有时您会发现,不管对代码如何改造,仍然有一些行、方法、甚至是整个类是测试不到的。下面是您可能会遇到的挑战的一些例子:

  • 只在特定平台上执行的代码。例如,在一个设计良好的 GUI 应用程序中,添加一个 Exit 菜单项的代码可以在 Windows PC 上运行,但它不能在 Mac 机上运行。

  • 捕获不会发生的异常的 catch 语句,比如在从 ByteArrayInputStream 进行读取操作时抛出的 IOException

  • 非公共类中的一些方法,它们永远也不会被实际调用,只是为了满足某个接口契约而必须实现。

  • 处理虚拟机 bug 的代码块,比如说,不能识别 UTF-8 编码。

考虑到上面这些以及类似的情况,我认为一些极限程序员自动删除所有未测试代码的做法是不切实际的,并且可能具有一定的讽刺性。不能总是获得绝对完美的测试覆盖率并不意味着就不会有更好的覆盖率。

然而,比执行不到的语句和方法更常见的是残留代码,它不再有任何作用,并且从代码基中去掉这些代码也不会产生任何影响。有时可以通过使用反射来访问私有成员这样的怪招来测试未测试的代码。还可以为未测试的、包保护(package-protected)的代码来编写测试代码,将测试类放到将要测试的类所在那个包中。但最好不要这样做。所有不能通过发布的(公共的和受保护的)接口访问的代码都应删除。执行不到的代码不应当成为代码基的一部分。代码基越小,它就越容易被理解和维护。

不要漏掉测量单元测试包和类本身。我不止一次注意到,某些个测试方法或者类没有被测试包真正运行。通常这表明名称规范中存在问题(比如将一个方法命名为 tesSomeReallyComplexCondition,而不是将其命名为 testSomeReallyComplexCondition),或者忘记将一个类添加到主 suite() 方法中。在其他情况下,未预期的条件导致跳过了测试方法中的代码。不管是什么情况,都是虽然已经编写了测试代码,但没有真正运行它。JUnit 不会告诉您它没有像您所想的那样运行所有测试,但是 Cobertura 会告诉您。找出了未运行的测试后,改正它一般很容易。





回页首


运行 Cobertura

在了解了测量代码覆盖率的好处后,让我们再来讨论一下如何用 Cobertura 测量代码覆盖率的具体细节。Cobertura 被设计成为在 Ant 中运行。现在还没有这方面的 IDE 插件可用,不过一两年内也许就会有了。

首先需要在 build.xml 文件中添加一个任务定义。以下这个顶级 taskdef 元素将 cobertura.jar 文件限定在当前工作目录中:

												
														<taskdef classpath="cobertura.jar" resource="tasks.properties" />
												
										

然后,需要一个 cobertura-instrument 任务,该任务将在已经编译好的类文件中添加日志代码。todir 属性指定将测量类放到什么地方。fileset 子元素指定测量哪些 .class 文件:

												
														<target name="instrument">
  <cobertura-instrument todir="target/instrumented-classes">
    <fileset dir="target/classes">
      <include name="**/*.class"/>
    </fileset>
  </cobertura-instrument>
</target>
												
										

用通常运行测试包的同一种类型的 Ant 任务运行测试。惟一的区别在于:被测量的类必须在原始类出现在类路径中之前出现在类路径中,而且需要将 Cobertura JAR 文件添加到类路径中:

												
														<target name="cover-test" depends="instrument">
  <mkdir dir="${testreportdir}" />
  <junit dir="./" failureproperty="test.failure" printSummary="yes" 
         fork="true" haltonerror="true">
    <!-- Normally you can create this task by copying your existing JUnit
         target, changing its name, and adding these next two lines.
         You may need to change the locations to point to wherever 
         you've put the cobertura.jar file and the instrumented classes. -->
    <classpath location="cobertura.jar"/>
    <classpath location="target/instrumented-classes"/>
    <classpath>
      <fileset dir="${libdir}">
        <include name="*.jar" />
      </fileset>
      <pathelement path="${testclassesdir}" />
      <pathelement path="${classesdir}" />
    </classpath>
    <batchtest todir="${testreportdir}">
      <fileset dir="src/java/test">
        <include name="**/*Test.java" />
        <include name="org/jaxen/javabean/*Test.java" />
      </fileset>
    </batchtest>
  </junit>
</target>>
												
										

Jaxen 项目使用 JUnit 作为其测试框架,但是 Cobertura 是不受框架影响的。它在 TestNG、Artima SuiteRunner、HTTPUni 或者在您自己在地下室开发的系统中一样工作得很好。

最后,cobertura-report 任务生成本文开始部分看到的那个 HTML 文件:

												
														<target name="coverage-report" depends="cover-test">
 <cobertura-report srcdir="src/java/main" destdir="cobertura"/>
</target>
												
										

srcdir 属性指定原始的 .java 源代码在什么地方。destdir 属性指定 Cobertura 放置输出 HTML 的那个目录的名称。

在自己的 Ant 编译文件中加入了类似的任务后,就可以通过键入以下命令来生成一个覆盖报告:

												
														% ant instrument
% ant cover-test
% ant coverage-report
												
										

当然,如果您愿意的话,还可以改变目标任务的名称,或者将这三项任务合并为一个目标任务。





回页首


结束语

Cobertura 是敏捷程序员工具箱中新增的一个重要工具。通过生成代码覆盖率的具体数值,Cobertura 将单元测试从一种艺术转变为一门科学。它可以寻找测试覆盖中的空隙,直接找到 bug。测量代码覆盖率使您可以获得寻找并修复 bug 所需的信息,从而开发出对每个人来说都更健壮的软件。





回页首


参考资料





回页首


关于作者

Elliotte Rusty Harold 出生在新奥尔良,现在他还定期回老家喝一碗美味的秋葵汤。不过目前,他和妻子 Beth 定居在纽约临近布鲁克林的 Prospect Heights,同住的还有他的猫咪 Charm(取自夸克)和 Marjorie(取自他岳母的名字)。他是 Polytechnic 大学计算机科学的副教授,讲授 Java 技术和面向对象编程。他的 Cafe au Lait 网站是 Internet 上最受欢迎的独立 Java 站点之一,姊妹站点 Cafe con Leche 已经成为最受欢迎的 XML 站点之一。他的著作包括 Effective XMLProcessing XML with JavaJava Network ProgrammingThe XML 1.1 Bible。目前他正在从事 XML 的 XOM API、Jaxen XPath 引擎和 Jester 测试覆盖率工具的开发工作。

posted @ 2006-07-19 12:52 eddy liao 阅读(261) | 评论 (0)编辑 收藏

http://www-128.ibm.com/developerworks/cn/java/j-cq01316/#N10149

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

您是否曾被测试覆盖度量引入歧途?

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

讨论


最新推荐

Java 应用开发源动力 - 下载免费软件,快速启动开发


级别: 初级

Andrew Glover , CTO, Vanward Technologies

2006 年 2 月 06 日

测试覆盖工具对单元测试具有重要的意义,但是经常被误用。这个月,Andrew Glover 会在他的新系列 —— 追求代码质量 中向您介绍值得参考的专家意见。第一部分深入地介绍覆盖报告中数字的真实含义。然后他会提出您可以尽早并经常地利用覆盖来确保代码质量的三个方法。

您还记得以前大多数开发人员是如何追求代码质量的吗。在那时,有技巧地放置 main() 方法被视为灵活且适当的测试方法。经历了漫长的道路以后,现在自动测试已经成为高质量代码开发的基本保证,对此我很感谢。但是这还不是我所要感谢的全部。Java™ 开发人员现在拥有很多通过代码度量、静态分析等方法来度量代码质量的工具。我们甚至已经设法将重构分类成一系列便利的模式!

要获得有关代码质量问题的答案,您可以访问由 Andrew Glover 主持的 Code Quality 论坛。

所有的这些新的工具使得确保代码质量比以前简单得多,不过您还需要知道如何使用它们。在这个系列中,我将重点阐述有关保证代码质量的一些有时看上去有点神秘的东西。除了带您一起熟悉有关代码质量保证的众多工具和技术之外,我还将为您说明:

  • 定义并有效度量最影响质量的代码方面。
  • 设定质量保证目标并照此规划您的开发过程。
  • 确定哪个代码质量工具和技术可以满足您的需要。
  • 实现最佳实践(清除不好的),使确保代码质量及早并经常地 成为开发实践中轻松且有效的方面。

在这个月,我将首先看看 Java 开发人员中最流行也是最容易的质量保证工具包:测试覆盖度量。

谨防上当

这是一个晚上鏖战后的早晨,大家都站在饮水机边上。开发人员和管理人员们了解到一些经过良好测试的类可以达到超过 90% 的覆盖率,正在高兴地互换着 NFL 风格的点心。团队的集体信心空前高涨。从远处可以听到 “放任地重构吧” 的声音,似乎缺陷已成为遥远的记忆,响应性也已微不足道。但是一个很小的反对声在说:

女士们,先生们,不要被覆盖报告所愚弄

现在,不要误解我的意思:并不是说使用测试覆盖工具是愚蠢的。对单元测试范例,它是很重要的。不过更重要的是您如何理解所得到的信息。许多开发团队会在这儿犯第一个错。

高覆盖率只是表示执行了很多的代码,并不意味着这些代码被很好地 执行。如果您关注的是代码的质量,就必须精确地理解测试覆盖工具能做什么,不能做什么。然后您才能知道如何使用这些工具去获取有用的信息。而不是像许多开发人员那样,只是满足于高覆盖率。


测试覆盖度量

测试覆盖工具通常可以很容易地添加到确定的单元测试过程中,而且结果可靠。下载一个可用的工具,对您的 Ant 和 Maven 构建脚本作一些小的改动,您和您的同事就有了在饮水机边上谈论的一种新报告:测试覆盖报告。当 foobar 这样的程序包令人惊奇地显示 覆盖率时,您可以得到不小的安慰。如果您相信至少您的部分代码可以保证是 “没有 BUG” 的,您会觉得很安心。但是这样做是一个错误。

存在不同类型的覆盖度量,但是绝大多数的工具会关注行覆盖,也叫做语句覆盖。此外,有些工具会报告分支覆盖。通过用一个测试工具执行代码库并捕获整个测试过程中与被 “触及” 的代码对应的数据,就可以获得测试覆盖度量。然后这些数据被合成为覆盖报告。在 Java 世界中,这个测试工具通常是 JUnit 以及名为 Cobertura、Emma 或 Clover 等的覆盖工具。

行覆盖只是指出代码的哪些行被执行。如果一个方法有 10 行代码,其中的 8 行在测试中被执行,那么这个方法的行覆盖率是 80%。这个过程在总体层次上也工作得很好:如果一个类有 100 行代码,其中的 45 行被触及,那么这个类的行覆盖率就是 45%。同样,如果一个代码库包含 10000 个非注释性的代码行,在特定的测试运行中有 3500 行被执行,那么这段代码的行覆盖率就是 35%。

报告分支覆盖 的工具试图度量决策点(比如包含逻辑 ANDOR 的条件块)的覆盖率。与行覆盖一样,如果在特定方法中有两个分支,并且两个分支在测试中都被覆盖,那么您可以说这个方法有 100% 的分支覆盖率。

问题是,这些度量有什么用?很明显,很容易获得所有这些信息,不过您需要知道如何使用它们。一些例子可以阐明我的观点。





回页首


代码覆盖在活动

我在清单 1 中创建了一个简单的类以具体表述类层次的概念。一个给定的类可以有一连串的父类,例如 Vector,它的父类是 AbstractListAbstractList 的父类又是 AbstractCollectionAbstractCollection 的父类又是 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() {
    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 String[this.classes.size()];        
    int x = 0;
    for(Iterator iter = this.classes.iterator(); iter.hasNext();){
       Class clzz = (Class)iter.next();
       names[x++] = clzz.getName();
    }        
    return names;
  }

  public Class getBaseClass() {
    return baseClass;
  }

  public void setBaseClass(final Class baseClass) {
    this.baseClass = baseClass;
  }
}

												
										

正如您看到的,清单 1 中的 Hierarchy 类具有一个 baseClass 实例以及它的父类的集合。清单 2 中的 HierarchyBuilder 通过两个复制 buildHierarchy 的重载的 static 方法创建了 Hierarchy 类。


清单 2. 类层次生成器
												
														package com.vanward.adana.hierarchy;

public class HierarchyBuilder {  

  private HierarchyBuilder() {
    super();		
  }

  public static Hierarchy buildHierarchy(final String clzzName) 
    throws ClassNotFoundException{
      final Class clzz = Class.forName(clzzName, false, 
          HierarchyBuilder.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(superclass != 
      null && superclass.getName().equals("java.lang.Object")){
       return hier; 
    }else{      
       while((clzz.getSuperclass() != null) && 
          (!clzz.getSuperclass().getName().equals("java.lang.Object"))){
             clzz = clzz.getSuperclass();
             hier.addClass(clzz);
       }	        
       return hier;
    }
  }      
}

												
										





回页首


现在是测试时间!

有关测试覆盖的文章怎么能缺少测试案例呢?在清单 3 中,我定义了一个简单的有三个测试案例的 JUnit 测试类,它将试图执行 Hierarchy 类和 HierarchyBuilder 类:


清单 3. 测试 HierarchyBuilder!
												
														package test.com.vanward.adana.hierarchy;

import 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);
     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 testBuildHierarchyNameAgain() {        
     Hierarchy hier = HierarchyBuilder.buildHierarchy(HierarchyBuilderTest.class);
     assertEquals("should be junit.framework.TestCase", 
       "junit.framework.TestCase", 
         hier.getHierarchyClassNames()[0]);      
  }
 
}

												
										

因为我是一个狂热的测试人员,我自然希望运行一些覆盖测试。对于 Java 开发人员可用的代码覆盖工具中,我比较喜欢用 Cobertura,因为它的报告很友好。而且,Corbertura 是开放源码项目,它派生出了 JCoverage 项目的前身。





回页首


Cobertura 的报告

运行 Cobertura 这样的工具和运行您的 JUnit 测试一样简单,只是有一个用专门逻辑在测试时检查代码以报告覆盖率的中间步骤(这都是通过工具的 Ant 任务或 Maven 的目标完成的)。

正如您在图 1 中看到的,HierarchyBuilder 的覆盖报告说明部分代码没有 被执行。事实上,Cobertura 认为 HierarchyBuilder 的行覆盖率为 59%,分支覆盖率为 75%。


图 1. Cobertura 的报告

这样看来,我的第一次覆盖测试是失败的。首先,带有 String 参数的 buildHierarchy() 方法根本没有被测试。其次,另一个 buildHierarchy() 方法中的两个条件都没有被执行。有趣的是,所要关注的正是第二个没有被执行的 if 块。

因为我所需要做的只是增加一些测试案例,所以我并不担心这一点。一旦我到达了所关注的区域,我就可以很好地完成工作。注意我这儿的逻辑:我使用测试报告来了解什么没有 被测试。现在我已经可以选择使用这些数据来增强测试或者继续工作。在本例中,我准备增强我的测试,因为我还有一些重要的区域未覆盖。

Cobertura:第二轮

清单 4 是一个更新过的 JUnit 测试案例,增加了一些附加测试案例,以试图完全执行 HierarchyBuilder


清单 4. 更新过的 JUnit 测试案例
												
														package test.com.vanward.adana.hierarchy;

import 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);
     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 testBuildHierarchyNameAgain() { 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(HierarchyBuilderTest.class);
     assertEquals("should be 2", 2, hier.getHierarchyClassNames().length);
  }

  public void testBuildHierarchyStrNotNull() throws Exception{
    Hierarchy hier = 
       HierarchyBuilder.
       buildHierarchy("test.com.vanward.adana.hierarchy.HierarchyBuilderTest");
    assertNotNull("object was null", hier);
  }

  public void testBuildHierarchyStrName() throws Exception{        
    Hierarchy hier = 
       HierarchyBuilder.
       buildHierarchy("test.com.vanward.adana.hierarchy.HierarchyBuilderTest");
    assertEquals("should be junit.framework.Assert", 
      "junit.framework.Assert",
        hier.getHierarchyClassNames()[1]);
  }

  public void testBuildHierarchyStrNameAgain() throws Exception{
    Hierarchy hier = 
       HierarchyBuilder.
       buildHierarchy("test.com.vanward.adana.hierarchy.HierarchyBuilderTest");
    assertEquals("should be junit.framework.TestCase", 
      "junit.framework.TestCase",
        hier.getHierarchyClassNames()[0]);      
  }

  public void testBuildHierarchyStrSize() throws Exception{        
     Hierarchy hier = 
        HierarchyBuilder.
        buildHierarchy("test.com.vanward.adana.hierarchy.HierarchyBuilderTest");
     assertEquals("should be 2", 2, hier.getHierarchyClassNames().length);        
  }

  public void testBuildHierarchyWithNull() {
     try{
       Class clzz = null;
       HierarchyBuilder.buildHierarchy(clzz);
       fail("RuntimeException not thrown");
     }catch(RuntimeException e){}
  }
}

												
										

当我使用新的测试案例再次执行测试覆盖过程时,我得到了如图 2 所示的更加完整的报告。现在,我覆盖了未测试的 buildHierarchy() 方法,也处理了另一个 buildHierarchy() 方法中的两个 if 块。然而,因为 HierarchyBuilder 的构造器是 private 类型的,所以我不能通过我的测试类测试它(我也不关心)。因此,我的行覆盖率仍然只有 88%。


图 2. 谁说没有第二次机会

正如您看到的,使用一个代码覆盖工具可以 揭露重要的没有相应测试案例的代码。重要的事情是,在阅读报告(特别 是覆盖率高的)时需要小心,它们也许隐含危险的信息。让我们看看两个例子,看看在高覆盖率后面隐藏着什么。





回页首


条件带来的麻烦

正如您已经知道的,代码中的许多变量可能有多种状态;此外,条件的存在使得执行有多条路径。在留意这些问题之后,我将在清单 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();
  }
}

												
										

您是否发现了清单 5 中有一个隐藏的缺陷呢?如果没有,不要担心,我会在清单 6 中写一个测试案例来执行 pathExample() 方法并确保它正确地工作:


清单 6. JUnit 来救援!
												
														package test.com.vanward.coverage.example01;

import junit.framework.TestCase;
import com.vanward.coverage.example01.PathCoverage;

public class PathCoverageTest extends TestCase {

  public final void testPathExample() {
    PathCoverage clzzUnderTst = new PathCoverage();
    String value = clzzUnderTst.pathExample(true);
    assertEquals("should be true", "true", value);
  }
}

												
										

我的测试案例正确运行,我的神奇的代码覆盖报告(如下面图 3 所示)使我看上去像个超级明星,测试覆盖率达到了 100%!


图 3. 覆盖率明星

我想现在应该到饮水机边上去说了,但是等等,我不是怀疑代码中有什么缺陷呢?认真检查清单 5 会发现,如果 conditionfalse,那么第 13 行确实会抛出 NullPointerExceptionYeesh,这儿发生了什么?

这表明行覆盖的确不能很好地指示测试的有效性。





回页首


路径的恐怖

在清单 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();
    }else{
      this.doIt();
    }
  }                             

  private void dontDoIt(){
    //don't do something...
  }

  private void doIt(){
    //do something!
  }   
}

												
										

呀!清单 8 中的 HiddenObject有害的。与清单 7 中一样,调用 doWork() 方法会导致 RuntimeException


清单 8. 上半部分!
												
														package com.acme.someotherpackage.HiddenObject;

public class HiddenObject {

  public static int doWork(){
    //return 1;
    throw new RuntimeException("surprise!");
  }
}

												
										

但是我的确可以通过一个良好的测试捕获这个异常!在清单 9 中,我编写了另一个好的测试,以图挽回我的超级明星光环:


清单 9. 使用 JUnit 规避风险
												
														package test.com.vanward.coverage.example02;

import junit.framework.TestCase;
import com.vanward.coverage.example02.AnotherBranchCoverage;

public class AnotherBranchCoverageTest extends TestCase {
    
  public final void testBranchIt() {
    AnotherBranchCoverage clzzUnderTst = new AnotherBranchCoverage();
    clzzUnderTst.branchIt(101);
  }    
}

												
										

您对这个测试案例有什么想法?您也许会写出更多的测试案例,但是请设想一下清单 7 中不确定的条件有不止一个的缩短操作会如何。设想如果前半部分中的逻辑比简单的 int 比较更复杂,那么 需要写多少测试案例才能满意?

仅仅给我数字

现在,对清单 7、8、9 的测试覆盖率的分析结果不再会使您感到惊讶。在图 4 的报告中显示我达到了 75% 的行覆盖率和 100% 的分支覆盖率。最重要的是,我执行了第 10 行!


图 4.愚弄的报酬

从第一印象看,这让我骄傲。但是这个报告有什么误导吗?只是粗略地看一看报告中的数字,会导致您相信代码是经过良好测试的。基于这一点,您也许会认为出现缺陷的风险很低。这个报告并不能帮助您确定 or 缩短操作的后半部分是一个定时炸弹!





回页首


质量测试

我不止一次地说:您可以(而且应该)使用测试覆盖工具作为您的测试过程的一部分。但是不要被覆盖报告所愚弄。关于覆盖报告您需要了解的主要事情是,覆盖报告最好用来检查哪些代码没有经过 充分的测试。当您检查覆盖报告时,找出较低的值,并了解为什么特定的代码没有经过充分的测试。知道这些以后,开发人员、管理人员以及 QA 专业人员就可以在真正需要的地方使用测试覆盖工具。通常有下列三种情况:

  • 估计修改已有代码所需的时间
  • 评估代码质量
  • 评定功能测试

现在我可以断定对测试覆盖报告的一些使用方法会将您引入歧途,下面这些最佳实践可以使得测试覆盖报告可以真正为您所用。

1. 估计修改已有代码所需的时间

对一个开发团队而言,针对代码编写测试案例自然可以增加集体的信心。与没有相应测试案例的代码相比,经过测试的代码更容易重构、维护和增强。测试案例因为暗示了代码在测试工作中是如何 工作的,所以还可以充当内行的文档。此外,如果被测试的代码发生改变,测试案例通常也会作相应的改变,这与诸如注释和 Javadoc 这样的静态代码文档不同。

在另一方面,没有经过相应测试的代码更难于理解和安全地 修改。因此,知道代码有没有被测试,并看看实际的测试覆盖数值,可以让开发人员和管理人员更准确地预知修改已有代码所需的时间。

再次回到饮水机边上,可以更好地阐明我的观点。

市场部的 Linda:“我们想让系统在用户完成一笔交易时做 x 工作。这需要多长时间。我们的用户需要尽快实现这一功能。”

管理人员 Jeff:“让我看看,这个代码是 Joe 在几个月前编写的,需要对业务层和 UI 做一些变动。Mary 也许可以在两天内完成这项工作。”

Linda:“Joe?他是谁?”

Jeff:“哦,Joe,因为他不知道自己在干什么,所以被我解雇了。”

情况似乎有点不妙,不是吗?尽管如此,Jeff 还是将任务分配给了 Mary,Mary 也认为能够在两天内完成工作 —— 确切地说,在看到代码之前她是这么认为的。

Mary:“Joe 写这些代码时是不是睡着了?这是我所见过的最差的代码。我甚至不能确认这是 Java 代码。除非推倒重来,要不我根本没法修改。”

情况对 “饮水机” 团队不妙,不是吗?但是我们假设,如果在这个不幸的事件的当初,Jeff 和 Mary 就拥有一份测试报告,那么情况会如何呢?当 Linda 要求实现新功能时,Jeff 做的第一件事就是检查以前生成的覆盖报告。注意到需要改动的软件包几乎没有被覆盖,然后他就会与 Mary 商量。

Jeff:“Joe 编写的这个代码很差,绝大多数没经过测试。您认为要支持 Linda 所说的功能需要多长时间?”

Mary:“这个代码很混乱。我甚至都不想看到它。为什么不让 Mark 来做呢?”

Jeff:“因为 Mark 不编写测试,刚被我解雇了。我需要您测试这个代码并作一些改动。告诉我您需要多长时间。”

Mary:“我至少需要两天编写测试,然后我会重构这个代码,增加新的功能。我想总共需要四天吧。”

正如他们所说的,知识的力量是强大的。开发人员可以在试图修改代码之前 使用覆盖报告来检查代码质量。同样,管理人员可以使用覆盖数据更好地估计开发人员实际所需的时间。

2. 评估代码质量

开发人员的测试可以降低代码中存在缺陷的风险,因此现在很多开发团队在新开发和更改代码的同时需要编写单元测试。然而正如前面所提到的 Mark 一样,并不总是在编码的同时进行单元测试,因而会导致低质量代码的出现。

监控覆盖报告可以帮助开发团队迅速找出不断增长的没有 相应测试的代码。例如,在一周开始时运行覆盖报告,显示项目中一个关键的软件包的覆盖率是 70%。如果几天后,覆盖率下降到了 60%,那么您可以推断:

  • 软件包的代码行增加了,但是没有为新代码编写相应的测试(或者是新增加的测试不能有效地覆盖新代码)。

  • 删除了测试案例。

  • 上述两种情况都发生了。

能够监控事情的发展,无疑是件好事。定期地查阅报告使得设定目标(例如获得覆盖率、维护代码行的测试案例的比例等)并监控事情的发展变得更为容易。如果您发现测试没有如期编写,您可以提前采取一些行动,例如对开发人员进行培训、指导或帮助。与其让用户 “在使用中” 发现程序缺陷(这些缺陷本应该在几个月前通过简单的测试暴露出来),或者等到管理人员发现没有编写单元测试时再感到惊讶(和愤怒),还不如采取一些预防性的措施。

使用覆盖报告来确保正确的测试是一项伟大的实践。关键是要训练有素地完成这项工作。例如,使每晚生成并查阅覆盖报告成为连续累计 过程的一部分。

3. 评定功能测试

假设覆盖报告在指出没有经过 足够测试的代码部分方面非常有效,那么质量保证人员可以使用这些数据来评定与功能测试有关的关注区域。让我们回到 “饮水机” 团队来看看 QA 的负责人 Drew 是如何评价 Joe 的代码的:

Drew 对 Jeff 说:“我们为下一个版本编写了测试案例,我们注意到很多代码没有被覆盖。那好像是与股票交易有关的代码。”

Jeff:“哦,我们在这个领域有好些问题。如果我是一个赌徒的话,我会对这个功能区域给予特别的关注。Mary 正在对这个应用程序做一些其他的修改 —— 她在编写单元测试方面做得很好,但是这个代码也太差了点。”

Drew:“是的,我正在确定工作的资源和级别,看上去我没必要那么担心了,我估计我们的团队会对股票交易模块引起足够的关注。”

知识再次显示了其强大的力量。与其他软件生命周期中的风险承担者(例如 QA)配合,您可以利用覆盖报告所提供的信息来降低风险。在上面的场景中,也许 Jeff 可以为 Drew 的团队提供一个早期的不包含 Mary 的所有修改的版本。不过无论如何,Drew 的团队都应该关注应用程序的股票交易方面,与其他具有相应单元测试的代码相比,这个地方似乎存在更大的缺陷风险。





回页首


测试有什么好处

对单元测试范例而言,测试覆盖度量工具是一个有点奇怪的组成部分。对于一个已存在的有益的过程,覆盖度量可以增加其深度和精度。然而,您应该仔细地阅读代码覆盖报告。单独的高覆盖率并不能确保代码的质量。对于减少缺陷,代码的高覆盖并不是必要条件,尽管高覆盖的代码的确更少 有缺陷。

测试覆盖度量的窍门是使用覆盖报告找出未经 测试的代码,分别在微观和宏观两个级别。通过从顶层开始分析您的代码库,以及分析单个类的覆盖,可以促进深入的覆盖测试。一旦您能够综合这些原则,您和您的组织就可以在真正需要的地方使用覆盖度量工具,例如估计一个项目所需的时间,持续监控代码质量以及促进与 QA 的协作。





回页首


参考资料





回页首


关于作者

Andrew Glover 是 Vanward Technologies 公司的 CTO,该公司位于华盛顿特区大都会地区,公司的专业领域是自动测试框架的构造,自动测试框架可以降低软件 bug 数量,减少集成和测试的时间,提高整体的代码稳定性。他还是 Java Testing Patterns(Wiley,2004 年 9 月)的合著者

posted @ 2006-07-19 12:48 eddy liao 阅读(189) | 评论 (0)编辑 收藏