现在 IT 开发h员比以往M时候都更加x试的重要性,没有l过良好试的代码更Ҏ出问题。在极限~程中,试驱动开发已l被证明是一U有效提高Y件质量的Ҏ。在试驱动的开发方式中QY件工E师在编写功能代码之前首先编写测试代码,q样能从最开始保证程序代码的正确性,q且能够在程序的每次演进时进行自动的回归试。测试对于Y件品的成|L臛_重要的作用,在极限编E领域,甚至有h提议M未经试的代码都应该自动从发布的产品中删除。作者ƈ不确信这个观Ҏ正确的,但是试本n的质量确实是一个需要高度关注的问题。测试的覆盖率是试质量的一个重要指标,我们需要工h帮助我们q行对Y件测试覆盖的考察?/p>
EclEmma 是q样一个能帮助开发h员考察试覆盖率的优秀?Eclipse 开源插件。EclEmma 在覆盖测试领域是如此的优UQ以致于它在q去不久?2006 q成Z Eclipse Community Awards Winners 册选手。虽然最?Eclipse Checkstyle Plugin 取得?Best Open Source Eclipse-based Developer tool 的称P但我们也可以由此看到 EclEmma 对开发h员的帮助是巨大的QEclipse Community Award 的具体信息可以参?参考资?/a>Q?/p>
提到 EclEmma 首先p说到著名?Java 覆盖试工具 Emma。Emma 是一个在 SourceForge 上进行的开源项目(参阅 参考资?/a>Q。从某种E度上说QEclEmma 可以看作?Emma 的一个图形界面。在本文?a >参考文?/a>中,可以看到专门讲述使用 Emma 的技术文章?/p>
Emma 的作者开?Emma 之初Q程序员已经有了各种各样优秀的开?Java 开发工兗D例来_我们有优U的集成开发环?EclipseQ有开源的 JDKQ有单元试工具 JUnitQ有 Ant q样的项目管理工P我们q可以用 CVS ?SubVersion 来进行源代码版本的维护。当时看来,也许唯一~少的就是一个开源的覆盖试工具了。Emma 是Z填补q项I白而生的。现在的情况已经?Emma 诞生的时候不一L。时至今日,我们已经有了不少的覆盖测试工兗例?Coverlipse 是一个基?Eclipse 的覆盖测试插件。其他还?CoberturaQQuilt ?JCoverage {。但?Emma h一些非怼U的特性得它更适合被广泛的使用。和 Coverlipse {工hhQEmma 是开源的Q同时它对应用程序执行速度的媄响非常小?/p>
EclEmma 的出现I补了 Emma 用户一个大的遗?---- ~Z囑Ş界面以及寚w成开发环境的支持。将 Eclipse ?Emma q两个在各自领域最ZU的工L合v来,q就?EclEmma 为我们提供的。接下来Q我们就要在后箋章节中和读者朋友一L?EclEmma 为开发h员提供了什么?/p>
![]() ![]() |
![]()
|
安装 EclEmma 插g的过E和大部?Eclipse 插g相同Q我们既可以通过 Eclipse 标准?Update 机制来远E安?EclEmma 插gQ?a >?1Q,也可以从站点Q参?参考资?/a>Q下?zip 文gq解压到 eclipse 所在的目录中?/p>
?1 d EclEmma 更新站点
不管采用何种方式来安?EclEmmaQ安装完成ƈ重新启动 Eclipse 之后Q工h上应该出C个新的按钮:
![]() ![]() |
![]()
|
Z实验 EclEmma 的特性,我们首先?Eclipse ?Workspace 中徏立一个名UCؓ test.emma 的新 Java 目。接下来Q我们在其中建立一?HelloWorld
c,其代码如下所C:
package test.emma; public class HelloWorld { /** * @param args */ public static void main(String[] args) { int rand = (int) (Math.random()*100); if(rand%2==0){ System.out.println( "Hello, world! 0"); } else System.out.println("Hello, world! 1"); int result = rand%2==0? rand+rand:rand*rand; System.out.println(result); } } |
接下来,我们通过 EclEmma q行 HelloWorld.main()
函数?/p>
?3 ?Java 应用E序q行覆盖试
执行完毕之后Q我们正在编?HelloWorld.java 的窗口将会变成如下所C:
?Java ~辑器中QEclEmma 用不同的色彩标示了源代码的测试情c其中,l色的行表示该行代码被完整的执行Q红色部分表C行代码根本没有被执行Q而黄色的行表明该行代码部分被执行。黄色的行通常出现在单行代码包含分支的情况Q例??4 中的 16 行就昄为黄艌Ӏ由于程序中有一个随机确定的分支Q因此读者的H口可能与这里稍有不同(11 行或?14 行中有且只有一个红色的行)?/p>
除了在源代码~辑H口直接q行着色之外,EclEmma q提供了一个单独的视图来统计程序的覆盖试率?/p>
?5 察看E序的覆盖测试率
EclEmma 提供?Coverage 视图能够分层的显CZ码的覆盖试率,?5 中的信息表明我们?HelloWorld 的一ơ运行覆盖了大约 68.6% 的代码?/p>
惛_一ơ运行中覆盖所有的代码通常比较困难Q如果能把多ơ测试的覆盖数据l合hq行察看Q那么我们就能更方便的掌握多ơ测试的试效果。EclEmma 提供了这L功能。现在,让我们重复数ơ对 HelloWorld 的覆盖测试。我们注意到 Coverage 视图L昄最新完成的一ơ覆盖测试。事实上QEclEmma 为我们保存了所有的试l果。接下来Q我们将通过 Coverage 视图的工h钮来l合多次覆盖试的结果?/p>
?6 用于l合多次覆盖试l果的工h按钮
当我们多ơ运?Coverage 之后Q我们可以单??6 所C工h按钮。之后,一个对话框被弹出以供用户选择需要合q的覆盖试?/p>
?7 选择需要合q的覆盖试l果
在合q完成之后,我们可以观察?Java ~辑器和 Coverage 视图中都昄了合q之后的l果Q?/p>
?8 察看合ƈ后的覆盖试l果
?8 中,我们可以看到Q通过多次q行覆盖试Q最l我们的代码辑ֈ?91.4% 的测试覆盖率。有的是,图中W三行代码被标记为红Ԍ而此行代码实际上是不可执行的。奥妙在于,我们没有生成M HelloWorld cȝ实例Q因此缺省构造函数没有被调用Q?EclEmma 这个特D代码的覆盖状态标记在cd明的W一行?/p>
![]() ![]() |
![]()
|
如果 EclEmma 只能试 Java Application 的测试覆盖率Q那么它相对命o行版本的 Emma 来说Q提供的增强׃多了。相反,EclEmma 提供了很多与 Eclipse 紧密l合的功能。它不仅能测?Java ApplicationQ还能计?JUnit 单元试Q对 Eclipse 插g试的覆盖率。从 ?9 中我们可以看?EclEmma 目前支持四种cd的程序?/p>
?9 EclEmma 的配|页?/strong>
Z了解 EclEmma 是如何获得覆盖测试数据的Q我们需要先?Emma 有初步的了解。通常代码覆盖试工具都需要对被执行的代码q行修改。?Emma 提供了两U方式来完成qg事?/p>
使用x插入模式的优点很明显Qclass 文g?jar 文g不会被修攏V而预插入模式的应用范围更为广泛,对于某些需要嵌入到框架中运行的代码来说Q例?EJBQ,我们只能使用预插入模式。EclEmma 仅仅使用?Emma 的预插入模式来工作,不过 EclEmma ~省会在临时目录中创?class 文g?jar 文g的副本来q行修改Q因此在 workspace ?class ?jar 文g仍然保持原样。虽然听上去很好Q但是由于需要修?classpath 来用修改过?class ?jar 文gQ对于不能修?classpath 的应用(例如 Eclipse RCP ?JUnit Plugin TestQ来_我们q是只能选择修改 workspace 中的 class 文g?jar 文g。对?Java Application ?JUnit cd的覆盖测试,我们可以在配|对话框中选中“In-place instrumentation”Ҏ指定直接修改 Workspace 中的 .class 文g?.jar 文g?/p>
![]() ![]() |
![]()
|
本文通过一个简单的例子介绍了?EclEmma q行覆盖试的基本过E。EclEmma 允许软g工程师方便的考察试的覆盖率Qƈ能将试l果以直观、简z的方式展现l开发h员?/p>
![]() |
||
|
![]() |
甘志QIBM 中国软g实验室(CSDL BJQChina Emerging Technology Institute 成员Q主要研I方向ؓ UML、Model driven development ?SOA。他在上交通大学计机p获得网l安全方向博士学位,期间发表了多论文和技术书c。你可以通过 ganzhi@cn.ibm.com 联系他?/p> |
0. 序言
Z码写注释一直是大多数程序员有些困扰的事情。当前程序员都能接受ZE序的可l护性、可L编码的同时写注释的说法Q但对哪些地方应该写注释Q注释如何写Q写多少{这些问题,很多E序员仍然没有答案。更头痛的是写文,以及l护文的问题,开发h员通常可以忍受~写或者改动代码时~写或者修改对应的注释Q但之后需要修正相应的文却比较困难。如果能从注释直接{化成文档Q对开发h员无疑是一U福韟뀂而doxygenp把遵守某U格式的注释自动转化为对应的文?/p>
Doxygen是基于GPL的开源项目,是一个非怼U的文系l,当前支持在大多数unixQ包括linuxQ,windows家族QMacpȝ上运行,完全支持C++, C, Java, IDLQCorba和Microsoft 家族Q语aQ部分支持PHP和C#语言Q输出格式包括HTML、latex、RTF、ps、PDF、压~的HTML和unix manpage。有很多开源项目(包括前两文章介l的log4cpp和CppUnitQ都使用了doxygen文pȝ。而国内的开发h员却使用的不多,q里从开发h员用的角度介绍q个工具Q开发h员用最的代h快掌握q种技术,q结合这个工h讨如何撰写注释的问题。以下以linux下的C++语言Zq行介绍Q以下讨论基于doxygen1.3.3?/p>
1. doxygen使用步骤
׃只是工具的用,q里不介l它的原理,直接从用步骤开始。Doxygen的用步骤非常简单。主要可以分为:
1Q第一ơ用需要安装doxygen的程?br> 2Q生成doxygen配置文g
3Q编码时Q按照某U格式编写注?br> 4Q生成对应文?br>doxygen的安装非常简单, linux下可以直接下载安装包q行卛_Q下载源代码~译安装也是比较通用的编译安装命令。请参考其安装文档完成安装?/p>
Doxygen在生成文时可以定义目属性以及文生成过E中的很多选项Q用下面命令能够生一个缺省的配置文gQ?br>doxygen -g [配置文g名]
可以Ҏ目的具体需求修攚w|文件中对应的项Q具体的修改q程在下面介l。修改过的配|文件可以作Z后项目的模板?/p>
让doxygen自动产生文Q^常的注释风格可不行,需要遵循doxygen自己的格式。具体如何写doxygen认识的注释在W?节详l介l?/p>
OKQ代码编完了Q注释也按照格式写好了,最后的文档是如何的哪?非常单,q行下面的命令,相应的文就会生在指定的目录中?br> doxygen [配置文g名]
需要注意的是doxygenq不处理所有的注释Qdoxygen重点x与程序结构有关的注释Q比如:文g、类、结构、函数、变量、宏{注释,而忽略函数内变量、代码等的注释?/p>
2. doxygen配置文g
doxygen配置文g的格式是也是通常的unix下配|文件的格式Q注?#'开始;tag = value [,value2…]Q对于多值的情况可以使用 tag += value [,value2…]?/p>
对doxygen的配|文件的修改分ؓ两类Q一U就是输出选项Q控制如何解释源代码、如何输出;一U就是项目相关的信息Q比如项目名U、源代码目录、输出文目录等。对于第一U设|好后,通常所有项目可以共用一份配|,而后一U是每个目必须讄的。下面选择重要的,有可能需要修改的选项q行解释说明Q其他选项在配|文仉有详l解释?/p>
TAG ~省?nbsp;含义
PROJECT_NAME 目名称
PROJECT_NUMBER 可以理解为版本信?br>OUTPUT_DIRECTORY 输出文g到的目录Q相对目录(doxygenq行目录Q或者绝对目?br>INPUT 代码文g或者代码所在目录,使用I格分割
FILE_PATTERNS *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp *.h++ *.idl *.odl 指定INPUT的目录中特定文gQ如Q?.cpp *.c *.h
RECURSIVE NO 是否递归INPUT中目录的子目?br>EXCLUDE 在INPUT目录中需要忽略的子目?br>EXCLUDE_PATTERNS 明确指定的在INPUT目录中需要忽略的文gQ如QFromOut*.cpp
OUTPUT_LANGUAGE English 生成文的语aQ当前支??0U语aQ国内用户可以设|ؓChinese
USE_WINDOWS_ENCODING YESQwin版本Q?br>NOQunix版本Q?nbsp;~码格式Q默认即可?br>EXTRACT_ALL NO 为NOQ只解释有doxygen格式注释的代码;为YESQ解析所有代码,即没有注释。类的私有成员和所有的静态项由EXTRACT_PRIVATE?EXTRACT_STATIC控制
EXTRACT_PRIVATE NO 是否解析cȝU有成员
EXTRACT_STATIC NO 是否解析静态项
EXTRACT_LOCAL_CLASSES YES 是否解析源文Ӟcpp文gQ中定义的类
SOURCE_BROWSER NO 如果为YESQ源代码文g会被包含在文中
INLINE_SOURCES NO 如果为YESQ函数和cȝ实现代码被包含在文?br>ALPHABETICAL_INDEX NO 生成一个字母序的列表,有很多类、结构等Ҏ设ؓYES
GENERATE_HTML YES 是否生成HTML格式文档
GENERATE_HTMLHELP NO 是否生成压羃HTML格式文Q?chmQ?br>GENERATE_LATEX YES 是否乘Rlatex格式的文?br>GENERATE_RTF NO 是否生成RTF格式的文?br>GENERATE_MAN NO 是否生成man格式文
GENERATE_XML NO 是否生成XML格式文
3. doxygen注释
3.1 注释风格
下面是工作量最大部分,安装doxygen格式写注释。通常代码可以附上一个注释块来对代码q行解释Q一个注释块׃行或者多行组成。通常一个注释块包括一个简要说明(briefQ和一个详l说明(detailedQ,q两部分都是可选的。可以有多种方式标识出doxygen可识别的注释块?br>1QJavaDoccd的多行注释?br>/**
* ….text….
*/
2QQT样式的多行注释?br>/*!
….text….
*/
3Q?/// …text….
4Q?//! …text….
要说明有多种方式标识Q这里推荐用@brief命o强制说明Q例如:
/**
* @brief [some brief description ]
* [ brief description more. ]
*
* [some more detailed description…]
*/
以上q些注释格式用来对紧跟其后的代码q行注释。doxygen也允许把注释攑ֈ代码后面Q具体格式是放一?<'到注释开始部分。例如:
int var1 ; /**< ….text…. */
int var2; ///< ….text….
注释和代码完全分,攑֜其他地方也是允许的,但需要用特D的命o加上名称或者声明进行标识,比如Qclass、struct、union、enum、fn、var、def、file、namespace、package、interfaceQ这些也是doxygenx的注释类型)。这里不推荐使用Q徏议注释尽量放在代码前后。具体用方式参见doxygen手册?/p>
3.2 doxygen常用注释格式
通常的选择上面的一、两U注释风|遇到头文件中各种cd定义Q关键变量、宏的定义,在其前或者后使用 @brief 定义其简要说明,IZ行后l箋写其详细的注释即可?/p>
对函数的注释Q是比较常常需要注释的部分。除了定义其要说明以及详l注释,q可以用param命o对其各个参数q行注释Q用return命o对返回D行注释。常见的格式如下Q?br>/**
*@brief func's brief comment.
*
* Some detailed comment.
*@param a [param a 's comment.]
*@param b [param b 's comment.]
*@exception std::out_of_range [exception's comment.]
*@return [return's comment.]
*/
int func1(int a, int b);
q行设计Ӟ通常有模块的概念Q一个模块可能有多个cL者函数组成,完成某个特定功能的代码的集合。如何对q个概念q行注释Qdoxygen提供了group的概念,生成的模块的注释会单独放在一个模块的面中。用下面的格式定义一个group?br>/** [group_name] [brief group description ]
* detailed group description ]
* @{
*/
code
/** @} */
group中的代码可以有自q注释。单U定义一个模块,去除{ 和}命o卛_。Q何其他代码项Q比如类、函数、甚xӞ如果要加入到某个模块Q可以在其doxygen注释中用ingroup命o卛_。Group之间使用ingroup命oQ可以组成树状关pR?br>/** @file util.cpp
* @ingroup [group_name]
* @brief file's brief info.
*/
把多个代码项一h加到某个模块中可以用addtogroup命oQ格式和defgroup怼?/p>
对于某几个功能类似的代码(比如cR函数、变量){,如果希望一h加注释,而又不想提升到模块的概念Q可以通过下面的方式:
//@{
/** Comments for all below code. */
code…
//@}
对这U组q行命名可以使用name命o。此时中间代码可以有自己的注释。如Q?br>/** @name group_name
* description for group.
*/
//@{
code…
//@}
3.3 doxygen常用注释命o
doxygen通过注释命o识别注释中需要特D处理的注释Q比如函数的参数、返回D行突出显C。上面也提到了一些注释命令(如:brief、param、return、以及group相关的命令)Q下面对其他一些常用的注释命oq行解释说明?br>@exception <exception-object> {exception description} 对一个异常对象进行注释?br>@warning {warning message } 一些需要注意的事情
@todo { things to be done } 对将要做的事情进行注?br>@see {comment with reference to other items } 一D包含其他部分引用的注释Q中间包含对其他代码的名称Q自动生对其的引用链接?br>@relates <name> 通常用做把非成员函数的注释文档包含在cȝ说明文档中?br>@since {text} 通常用来说明从什么版本、时间写此部分代码?br>@deprecated
@pre { description of the precondition } 用来说明代码的前提条g?br>@post { description of the postcondition } 用来说明代码之后的使用条g?br>@code 在注释中开始说明一D代码,直到@endcode命o?br>@endcode 注释中代码段的结束?/p>
到此为止Q常用的doxygen的注释格式讨论完毕,我们能够按照一定的格式撰写doxygen认识的注释,q能够用doxygen方便快捷的生成对应的文Q不q注释中应该写些什么,如何撰写有效的注释可能是困扰开发h员的一个更深层ơ的问题?/p>
4. 注释的书?br>注释应该怎么写,写多q是写少。过多的注释甚至会干扰对代码的阅诅R写注释的一个ȝ原则是注释应该量用来表明作者的意图Q至也应该是对一部分代码的ȝQ而不应该是对代码的重复或者解释。对代码的重复或者解释的代码Q看代码可能更容易理解。反映作者意囄注释解释代码的目的,从解决问题的层次上进行注释,而代码ȝ性注释则是从问题的解{的层次上进行注释?/p>
推荐的写注释的过E是首先使用注释勑Z码的主要框架Q然后根据注释撰写相应的代码。对各种主要的数据结构、输出的函数、多个函数公用的变量q行详细地注释。对代码中控制结构,单一目的的语句集q行注释。下面是一些写注释旉要注意的要点Q?br> 避免对单独语句进行注释;
通过注释解释Z么这么做、或者要做什么,使代码的读者可以只阅读注释理解代码Q?br> 对读者可能会有疑问的地方q行注释Q?br> Ҏ据定义进行注释,而不是对其用过E进行注释;
对于难于理解的代码,q行改写Q而不要试N过注释加以说明Q?br> 对关键的控制l构q行注释Q?br> Ҏ据和函数的边界、用前提等q行注释Q?/p>
5. 参考资?br> 1. doxygen homepage
http://www.stack.nl/~dimitri/doxygen/
2. doxygen manual
http://www.stack.nl/~dimitri/doxygen/manual.html
3. Code Complete: A Practical Handbook of Software Construction. Redmond, Wa.: Microsoft Press, 880 pages, 1993. ISBN: 1-55615-484-4.
4. 介doxygen
http://www.stack.nl/~dimitri/doxygen/doxygen_intro_cn.html
5. 10 Minutes to document your code
http://www.codeproject.com/tips/doxysetup.asp
6. 使用doxygen
http://www.csdn.net/Develop/article/16%5C16383.shtm
6. 关于作?br>mounton @ {www.ihere.org} 当前x于网l安全品的开发、研IӞ软g开发过E等斚w。您可以通过mount0n@yahoo.com和他联系?br>