JUnit 是一个开源的 Java 测试框架,可以帮助开发人员简化测试案例的编写,已成为 Java 社区中知名度很高的单元测试工具。
Apache Ant 是一个基于 Java 的构建工具,它凭借出色的易用性、平台无关性以及对项目自动测试和自动部署的支持,成为众多项目构建过程中不可或缺的工具,并已经成为事实上的标准。大多数现代 Java IDE(包括 Eclipse)都支持 Ant 构建文件的开发,也都支持在 IDE 内部运行这些文件;而 Ant 也可以独立于任何 IDE 运行。
Ant 内置了对 JUnit 的支持,它提供了两个 Task:junit 和 junitreport,分别用于执行 JUnit 单元测试和生成测试结果报告。测试人员使用生成的测试结果报告,可以很方便地查看并分析测试结果。下图为 Ant 生成的 HTML 格式的测试结果报告。
图 1. Ant 生成的 HTML 格式的测试结果报告
(查看图 1 的 清晰版本。)
在生成的测试结果报告中,可以看到每个测试套件的软件包和类名、测试案例的故障和错误数,以及测试套件的执行时间。对于每个测试套件,可以看到如下信息:
- 测试案例的名称
- 测试结果
- 故障或错误的类型(如果适用)
- 任何故障或错误的详细信息
- 执行的持续时间
但是,我们无法在上述测试结果报告中看到测试案例相对应的 Defect(缺陷)信息。这个信息在用户查看并分析测试结果报告的时候至关重要:
- 对于成功的测试案例,用户可以看到它的 defect 历史信息;
- 对于失败的测试案例,用户可以很快知道这个失败的测试案例有没有对应的 defect;
- 对于新失败的测试案例,用户可以方便知道是不是产生了 regression defect;
为了在测试结果报告中添加测试案例相对应的 defect 信息,下面将介绍如何利用 Java Annotation,并扩展 Ant 来实现这个目标。
首先,需要定义一个 Annotation 表示 defect,并给测试案例加上这个 annotation。
清单 1. 定义一个 annotation: Defect
import java.lang.annotation.Target; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy; @Documented @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @Interface Defect { public String value(); } |
定义好了 Defect annotation 后,用 defect annotation 给测试案例加上相对应的 defect(如果有的话)。
清单 2. 给测试案例加上 defect annotation
@Defect(“DDDL7Z76CX”) public void testRenameDocumentChangingExtension() throws Exception { //test logic here } |
Ant 的最大优势之一是它的可扩展性。对于使用带有 XML 格式化器( <formatter type="xml"/> )的 <junit> 任务运行的每个测试类,都创建了一个 XML 文件。
为了捕获测试案例对应的用 annotation 定义的 defect 信息,我们需要扩展 Ant 的 JUnit 相关类中的 XMLJUnitResultFormatter 来添加 defect 信息。 XMLJUnitResultFormatter 类中添加的部分用 /*-- ADDED --*/ 标明。
清单 3. XMLJUnitResultFormatter 中的修改部分
/** * Interface TestListener. * * A Test is finished. * @param test the test. */ public void endTest(Test test) { if (!testStarts.containsKey(test)) { startTest(test); } Element currentTest = null; if (!failedTests.containsKey(test)) { currentTest = doc.createElement(TESTCASE); String n = JUnitVersionHelper.getTestCaseName(test); currentTest.setAttribute(ATTR_NAME, n == null ? UNKNOWN : n); // a TestSuite can contain Tests from multiple classes, // even tests with the same name - disambiguate them. currentTest.setAttribute(ATTR_CLASSNAME, JUnitVersionHelper.getTestCaseClassName(test)); /*-- ADDED --*/ // 获取defect信息 Method method = null; try { method = test.getClass().getMethod(n, new Class [0]); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } boolean hasDefect = method.isAnnotationPresent(DefectAnnotation.Defect.class); //若有defect信息,添加到生成的XML文件 if (hasDefect) { Defect defectAnnotation = (Defect)method. getAnnotation(DefectAnnotation.Defect.class); String defectNumbers = defectAnnotation.value(); currentTest.setAttribute("defects",defectNumbers); } /*-- END ADDED --*/ rootElement.appendChild(currentTest); testElements.put(test, currentTest); } else { currentTest = (Element) testElements.get(test); } Long l = (Long) testStarts.get(test); currentTest.setAttribute(ATTR_TIME, "" + ((System.currentTimeMillis() - l.longValue()) / ONE_SECOND)); } |
作为附带的优点,当扩展捕获的数据时,最终捕获的不仅是在测试套件运行时特定状态的信息,而且还包括了用户自定义的属性。这样,在生成的 XML 文件中就会包含测试案例对应的 defect 信息。
使用自定义的 XSLT 在生成的测试结果报告中显示 defect
junitreport task 使用 XSLT 把 junit task 生成的 xml 文件生成 HTML 格式的测试结果报告。<junitreport> 可被轻松扩展,允许用户自定义的 XSLT 文件用作报告生成。我们可以在 <junitreport> 中嵌套的 <report> 标签中用“styledir”属性指定用户自定义的 XLST 文件所在的目录。
<!-- 使用 reportstyle/junit-frames.xsl 生成测试报告 --> <report styledir="reportstyle" format="frames" todir="testreport"/> |
定制测试报告,有两种方法:
- XSLT 文件内建于 Ant 的 optional.jar 文件中。将它解压缩到本地目录,然后修改 junit-frames.xsl 文件以便将 defect 信息引入报告。
- 创建一个自定义的 XSLT 文件,然后在 <junitreport> 中嵌套的 <report> 标签中用“styledir”属性指定用户自定义的 XLST 文件所在的目录。对于一些小的修改,创建一个自定义的 XSLT 文件,引入默认的 XSLT, 并且覆盖需要定制的 templates 是一个不错的方法。例如,在每一个测试案例中加一列(defect 信息), 我们就只需要重载产生表头和表行的 template 就可以了。
在下面的清单中,是重载产生表头和表行来加 defect 的例子(添加的部分用 <!-- ADDED --> 标明)。
清单 4. 自定义的 XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- import the default stylesheet --> <xsl:import href="jar:file:lib/ant-junit.jar!/org/apache/tools/ant/ taskdefs/optional/junit/xsl/junit-frames.xsl"/> <!-- override the template producing the test table header --> <!-- method header --> <xsl:template name="testcase.test.header"> <xsl:param name="show.class" select="''"/> <tr valign="top"> <xsl:if test="boolean($show.class)"> <th>Class</th> </xsl:if> <th>Name</th> <th>Status</th> <th width="80%">Type</th> <!-- ADDED --> <th width="80%">Defects</th> <th nowrap="nowrap">Time(s)</th> </tr> </xsl:template> <!-- override the template producing a test table row --> <xsl:template match="testcase" mode="print.test"> <xsl:param name="show.class" select="''"/> <tr valign="top"> <xsl:attribute name="class"> <xsl:choose> <xsl:when test="error">Error</xsl:when> <xsl:when test="failure">Failure</xsl:when> <xsl:otherwise>TableRowColor</xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:variable name="class.href"> <xsl:value-of select="concat(translate(../@package,'.','/'), '/', ../@id, '_', ../@name, '.html')"/> </xsl:variable> <xsl:if test="boolean($show.class)"> <td> <a href="{$class.href}"> <xsl:value-of select="../@name"/> </a> </td> </xsl:if> <td> <a name="{@name}"/> <xsl:choose> <xsl:when test="boolean($show.class)"> <a href="{concat($class.href, '#', @name)}"> <xsl:value-of select="@name"/> </a> </xsl:when> <xsl:otherwise> <xsl:value-of select="@name"/> </xsl:otherwise> </xsl:choose> </td> <xsl:choose> <xsl:when test="failure"> <td>Failure</td> <td> <xsl:apply-templates select="failure"/> </td> </xsl:when> <xsl:when test="error"> <td>Error</td> <td> <xsl:apply-templates select="error"/> </td> </xsl:when> <xsl:otherwise> <td>Success</td> <td></td> </xsl:otherwise> </xsl:choose> <td> <!-- ADDED --> <xsl:call-template name="display-defects"> </xsl:call-template> </td> <td> <xsl:call-template name="display-time"> <xsl:with-param name="value" select="@time"/> </xsl:call-template> <!-- ADDED --> <xsl:template name="display-defects"> <xsl:choose> <xsl:when test="not(@defects)">N/A</xsl:when> <xsl:otherwise> <xsl:value-of select="@defects"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> |
以上整个工作只涉及到修改一个现有的类 (XMLJunitResultFormatter.java),添加一个新的类 (DefectAnnotation.java) 并创建一个自定义的 XSLT 文件,之后就可以开始部署的工作(这些文件可以在下载的 zip 包中看到)。将修改之后的类文件编译好,连同自定义的 XSLT 文件打包到 Ant-Junit.jar 中去替代原有的 class 就可以了。
为了使用这个新的 Ant-Junit.jar,可以拷贝新的 Ant-Junit.jar 到 Ant\lib 目录中,也可以在运行 Ant 的时候,用 -lib 参数来指定你的新 Ant-Junit.jar 。这样,就可以在生成的测试结果报告中看到测试案例对应的 defect 信息了。
包含 defect 信息的测试结果报告如下。
图 2. 包含 defect 信息的 Ant JUnit Report
(查看图 2 的 清晰版本。)