注:本文源地址http://www.ibm.com/developerworks/cn/java/j-lo-antjunit-ext/index.html
尽管 Ant 自带的 JUnit task 命令可以非常方便的进行测试用例的选择,但是有些情况下依然无法满足特定工程的需要。由于 Ant 自身的良好的扩展性,开发者可以扩展 Ant JUnit,使它能够通过设置正则表达式来支持更灵活的选择。在了解了 Ant 的扩展机制之后,扩展的过程其实比较轻松。更好的是,扩展之后的 Ant JUnit 命令能够保持对原有命令的完全兼容性。
了解 Ant JUnit
Apache Ant 是一个基于 Java 的 build 工具,它使用 XML 来配置命令 (Task) 。 Ant 提供了非常丰富的预定义命令,所以在大多数的情况下,开发者只需要使用 Ant 自带的命令就能完成绝大多数的功能。但如果在某些特定的情况下,为了让 Ant 能够实现一些额外的功能,开发者可以扩展预定义的命令,或者自行开发新的命令。而 Ant 的设计者也充分考虑了这一点,易扩展的 Ant 体系使得这个工作也变得非常轻松。
和 Ant 一样,JUnit 也是一个非常流行的工具,在单元测试领域几乎是事实上的工业标准。几乎所有的开发工具都对 JUnit 有很好的支持,当然 Ant 也不例外。 Ant 为 JUnit 提供了一个命令 (JUnit Task),能够让开发者轻松的指定被测试的类、输出格式和运行方式等。清单 1 是一个典型的 Ant JUnit 配置。
清单 1 一个标准 Ant JUnit 的例子
<target name="test" depends="compile" description="Execute the unit test">
<mkdir dir="${report.dir}"/>
<property name="cases" value="*Test"/>
<junit printsummary="yes" fork="yes" jvm="${fvt.java.home}/bin/java">
<classpath refid="classpath"/>
<formatter type="plain" usefile="false"/>
<formatter type="xml"/>
<!--<test name="${example.ut.test}" todir="${report.dir}"/>-->
<batchtest todir="${report.dir}">
<fileset dir="${src.dir}">
<patternset>
<include name="**/feature1*.java" />
<include name="**/testcase?.java" />
</patternset>
</fileset>
</batchtest>
</junit>
<target>
在清单 1 的这个 Ant XML 中,请注意到 <batchtest> 中可以包含 <fileset> 等表示的文件集合,可以把 <batchtest> 理解成为一个执行测试用例的容器,只要把测试用例的集合送给它,它就能够不折不扣的去执行所有的测试用例。另外还可以看到 Ant 除了支持常用的通配符 (wildcard)* 和 ? 之外,还支持一个任意匹配的符号 ** 。通过这几个通配符的组合使用,Ant JUnit 能够满足大多数单元测试的需要。但是在某些特定的实际项目中,默认的 Ant JUnit 命令可能无法满足要求。让我们看看下面的工程。
实际项目的需求
图 1 一个单元测试项目的需求
在图 1 的这个工程中,有两个不同的产品 product 和 product2 。两个产品各自有不同的 feature,在每个 feature 中还包括不同级别 (level) 的单元测试 TestCaseLX 。在实际的测试中,我们很有可能灵活的选择要测试的 feature 和级别。比如星期一,我们要选择 feature1 和 feature3,需要运行所有的级别,但是星期二我们就要运行所有的 L1 的测试用例。
这个工程的特定需求要求我们在选择测试用例的时候同时考虑测试用例的 level,所处的 product 和 feature 。如果使用 Ant JUnit 默认的命令,配置出各种各样测试用例组合并不容易。所以可以考虑对 Ant JUnit 命令进行扩展。
在动手做具体的工作之前,让我们先看看如何去扩展一个 Ant 命令。所有细节都在 Ant 的在线文档中都有非常清楚地叙述,在这里笔者只是提取出一些扩展需要知道的关键性概念。
- 每个 Ant 命令在代码中是对应着一个扩展 org.apache.tools.ant.Task 的类;
- 每个 Ant 命令的执行过程对应于 execute() 方法;
- 如果某命令中有嵌套的子元素 (nested element),比如 abc,你需要为这个嵌套子元素创建一个对应类 Abc 。并在父元素的对应类中创建一个方法 createAbc() 用来表示该嵌套元素的创建过程,该方法将返回一个新创建出来的 Abc 。 <abc> 的内部的属性和元素,都应在类 Abc 中去处理。
扩展 Ant JUnit
现在我们开始扩展 Ant JUnit 。这个过程分成如下的四个步骤。
- 明确目标:首先要明确我们扩展之后的 Ant JUnit 命令应该是什么样子的;
- 准备源代码:然后要获取 Ant JUnit 的源代码,并准备 Java 工程;
- 扩展:在该 Java 工程中进行具体的扩展工作;
- 部署:制作最终的 Jar 并应用
明确目标
为了满足项目的需要,最简单的方式是让 Ant JUnit 能够支持正则表达式。我们可以通过送入不同的正则表达式来满足不同的匹配需求。而正则表达式的编写本身是一个不难的工作。
为了实现这个目标,一个很好的实践是先定义出目标 Ant XML,之后根据这个 XML 所需要的属性来扩展 Ant JUnit 。在这个例子中,假定将会选择 product 的 feature1 和 feature2 的所有 L1 和 L2 的测试用例进行测试。可以看到,我们为 batchtest 增加了一个子元素 freeselector,它的 include 属性是一个非常简单的正则表达式。如果要选择其他的测试用例进行测试,只需要修改 freeselector 的 include 属性即可。如清单 2 所示。
清单 2 目标 Ant 文件
<target name="test" depends="compile" description="Execute the unit test">
<mkdir dir="${report.dir}"/>
<property name="cases" value="*Test"/>
<junit printsummary="yes" fork="yes" jvm="${fvt.java.home}/bin/java">
<classpath refid="classpath"/>
<formatter type="plain" usefile="false"/>
<formatter type="xml"/>
<batchtest todir="${report.dir}">
<freeselector
dir="${src.dir}"
include=".*/product/(feature1|feature2)/.*L(1|2).java" />
</batchtest>
</junit>
</target>
准备源代码
本次扩展是增强 Ant JUnit 命令的功能,而不是重新制作一个 Ant 命令。首先需要从 Ant 的站点上下载 Ant 的源代码(http://apache.mirror.phpchina.com/ant/source/apache-ant-1.7.1-src.zip),把其中 JUnit 相关的部分摘取出来,在 Eclipse IDE 中建立一个 Java 工程来对它进行编辑。注意,本文的例子是在 Ant 1.7.1 的基础上完成,如果读者使用 Ant 其他版本,请根据实际情况来进行调整。另外,本文使用并修改了 Ant 的源代码,请务必根据项目的实际情况考虑相应的版权和许可问题。
Ant 的所有源代码都放在 %src_build_root%\src\main 目录下,而 JUnit 命令则是放在 org.apache.tools.ant.taskdefs.optional.junit 这个 package 下。将 Junit Package 下的源代码拷贝到你的 Java 工程下,并且为它提供相应的 JUnit/Ant 库支持。这样,你就已经为所有的扩展工作做好准备了。
扩展
如果您已经了解了 Ant 的扩展的基本原理,那么扩展 Ant JUnit 的过程也就显得不是很复杂了。 <freeselector> 元素是 <batchtest> 的子元素,根据 <batchtest> 的执行容器的特点,<freeselector> 本质上要做的事情就是提供一个资源列表的选择,剩下的其他工作 <batchtest> 都会帮你完成。
为此我们创建一个类叫做 FreeSelector,该类实现一个叫做 ResourceCollection 的接口。在 Ant 中,ResourceCollection 代表了一个被选择的资源集合,而 FreeSelector 正是要为 <batchtest> 提供一个被执行的单元测试集合。之后为该类创建出两个属性 include 和 dir,并提供它们的 getter 和 setter 方法。如清单 3 所示。
清单 3 创建 FreeSelector 类的框架
public class FreeSelector implements ResourceCollection
{
private String dir=null;
private String include=null;
public String getDir()
{
return dir;
}
public void setDir(String dir)
{
this.dir = dir;
}
public String getInclude()
{
return include;
}
public void setInclude(String include)
{
this.include = include;
}
}
在完成 FreeSelector 类的框架之后,要为它实现 ResourceCollection 所定义的所有方法,如清单 4 所示。
清单 4 为 FreeSelector 实现 ResourceCollection 的方法
List list = null;
public Iterator iterator()
{
if (list == null)list = getList();
return list.iterator();
}
public int size()
{
if (list == null)list = getList();
return list.size();
}
在清单 4 中,我们把 iterator 和 size 两个方法的具体实现都代理给了另外一个方法 getList 。该列表返回 FreeSelector 对应的资源列表。该方法在清单 5 中得到了实现。实现的基本方法是在 dir 目录下进行枚举,并把所有搜索到的文件资源和 include 属性对应的正则表达式进行匹配,匹配成功的文件资源就被增加到集合中。
清单 5 为 FreeSelector 实现 getList 方法
private List getList()
{
String pattern = include;
Pattern p = Pattern.compile(pattern); //compile the "include" pattern
File baseDir = project.getBaseDir();
File srcFile = new File(baseDir, dir); //get the base dir for all testcases
Set l = new HashSet();
Traveller t = new Traveller(srcFile, l, p);
t.travel(); //deep-travel the base dir to get all matched resouces
List fl=new ArrayList();
Iterator itr=l.iterator();
while(itr.hasNext())
{ //wrap the matched list to be Ant resource
Object obj=itr.next();
File f=(File)obj;
if(f!=null)
{
FileResource fr=new FileResource(f);
fr.setBaseDir(srcFile);
fl.add(fr);
}
}
return fl;
}
清单 5 中,Traveller 是一个广度优先的文件树的遍历器(具体代码可以参考附件中的源代码),对于每个被遍历的文件资源,清单 6 的方法被用来检测它是否符合 include 正则表达式的要求。
清单 6 资源遍历器的判定资源合法性的方法
void process(File f)
{
String path = f.getAbsolutePath();
path = path.replaceAll("\\\\", "\\/");
if (p.matcher(path).matches())
{
list.add(f);
}
}
最后,别忘了在 BatchTest 类中增加一个方法,用来关联 <batchtest> 和 <freeselector> 。如清单 7 所示。
清单 7 在 BatchTest 类中增加 FreeSelector 的入口
public void addFreeselector(FreeSelector fs)
{
add(fs);
if(fs.getProject()==null) fs.setProject(project);
}
部署
以上整个源代码工作只涉及到一个新的类 (FreeSelector) 和一个现有的类 (BatchTest) 。之后就可以开始部署的工作。将修改之后的类文件编译好,并且打包到 Ant-Junit.jar 中去替代原有的 class 就可以了。
为了使用这个新的 Ant-Junit.jar,可以拷贝新的 Ant-Junit.jar 到 Ant\lib 目录中,也可以在运行 Ant 的时候,用 -lib 参数来指定你的新 Ant-Junit.jar 。这样,清单 2 所示的 Ant XML 文件就可以正确地运行了。
小结
Ant 是一个非常易于扩展的体系,为 Ant 现有的命令增加新的特性,或者增加新的命令都是非常方便的。本文展示了如何增加 Ant JUnit 对于正则表达式的支持,从而简洁而灵活地解决了项目中的实际问题,读者可以灵活把它应用到具体的项目工作当中。
下载
描述 |
名字 |
大小 |
下载方法 |
本文代码下载 |
MyAntTask.zip |
81 KB |
HTTP |
参考资料
关于作者
杨志宝,现为 IBM 中国软件开发中心的软件工程师,Websphere Information Integrator 项目组,从事 Rational Data Architect 的测试和 Rational Functional Tester 的相关开发工作。