JUnit 测试框架被越来越多的开发小组所共同使用归功于各种各样的测试装具模块现在可以测试构成任何 Java 应用程序的几乎每一个组件事实上几乎整个二级市场似乎都是用围绕 Junit 建立的包括 CactusjfcUnitXMLUnitDbUnit 和 HttpUnit 这样的装具模块都可以免费供开发人员用于测试应用程序随着系统的复杂程度的增加并且有这么多工具可供使用没有什么理由不依靠单元测试
不过开发人员不仅仅是程序员我们与用户交互以修复 bug 并确定需求我们参加会议并进行电话推销我们完成一些(有时全部)质量保证功能既然有这么多责任希望尽可能自动化就是自然而然的了因为好的团队(除了其他事情外)会进行大量测试希望自动化不同的开发过程的人常常会对这一领域进行详细研究
自动化单元测试
有许多种自动化所有项目测试用例的定位和执行的方法一种解决方案是联合使用 Ant 的 junit 任务与嵌入的 fileset 任务这样就可以包括和排除特定目录中的文件(基于文件名样式)另一种选择是使用 Eclipse 的一个功能它可以指定所有测试所在的和执行的目录前一种选择提供了对运行的测试进行过滤的灵活性(并且由于它是一个纯粹的无头(headless)Java 应用程序可以运行在几乎所有地方)后一种选择可以调试动态包是否可以结合这两种方式的强大和灵活性?
有了 Python 编程语言的 Java 平台实现Jython回答是响亮的可以!(如果不熟悉 Jython开发应当在继续本文之前补充这方面知识更多信息请参阅后面的 参考资料)利用 Jython 的强大和优雅可以维护一个定位文件系统搜索匹配某种样式的类和动态编译 JUnit TestSuite 类的脚本这个 TestSuite 类像所有其他静态定义的类一样可以用喜爱的调试程序容易地调试(在本文中使用的例子假定使用的是 Eclipse IDE不过我在这里描述的技术不用做很多修改就可以用于大多数其他 IDE)
在进行任何设计决定时必须对所做的选择和决定的影响进行权衡在这里为了得到调试动态生成的测试包的能力必须增加额外的复杂性不过这种复杂性被 Jython 自身所减轻了Jython 经过很好测试并得到很好的支持并且是开放源代码的而且Python 越来越成为面向对象的平台独立的编程的事实上的标准出于这两种原因采用 Jython开发 的风险很少特别是它提供了这样的好处在创建和调试动态生成的 JUnit TestSuite 类方面具有无可匹敌的灵活性
如果是否采用 Jython 是主要的考虑那么即使不使用它也可以在解决原来的问题方面有所进展不使用 Jython 的话可以用一个 Java Property 文件存储一组类目录和包以在包中加入或者排除测试不过如果选择使用 Jython就可以利用整个 Python 语言和运行时来解决选择执行哪些测试的问题Python 脚本比 Java Property 文件灵活得多它只受限于您的想像力
利用 Jython 与 Java 平台的无缝集成可以创建静态定义的然而是动态构建的 TestSuite 类有大量关于 JUnit 的教程不过还是看下面这两行代码作为复习清单 是静态构建 TestSuite 类的一个例子(这个例子取自 JUnit: A Cooks Tour有关它和其他 JUnit 资源的链接请参阅 参考资料)
清单 静态定义 TestSuite
public static Test suite() {
return new TestSuite( MoneyTestclass )
}
清单 表明 TestSuite 是由 Test 类的类实例组成的这个装具模块完全利用了这一点为了分析这个工具的代码应从 参考资料中下载本文的示例 JAR 文件这个文档包含两个文件DynamicTestSuitejava 和 getalltestspy前者是一个用 Phthon 脚本动态生成 TestSuite 的 JUnit 测试装具模块后者是一个搜索匹配特定样式的文件的 Python 脚本DynamicTestSuitejava 使用 getalltestspy 构建 TestSuite 可以修改 getalltestspy 以更好地适合自己的项目的需要
了解测试装具模块
代码是如何工作的?首先指派 getalltestspy 获取一组要执行的 Test 类然后使用 Jython API 将这个列表从 Python 运行时环境中提取出来然后使用 Java Reflection API 构建在表示 Test 类名的列表中的 String 对象的类实例最后用 JUnit API 将 Test 添加到 TestSuite 中这四个库的相互配合可以实现您的目标动态构建的 TestSuite 可以像静态定义的那样运行
看一下清单 中的 JUnit suite 清单它是一个公开 public static TestSuite suite() 方法签名的 TestCase 由 JUnit 框架调用的 suite() 方法调用 getTestSuite() getTestSuite() 又调用 getClassNamesViaJython() 以获取一组 String 对象其中每一个对象表示一个作为包的一部分的 TestCase 类
清单 动态定义 TestSuite
/**
* @return TestSuite A test suite containing all our tests (as found by Python script)
*/
private TestSuite getTestSuite() {
TestSuite suite = new TestSuite()
// get Iterator to class names were going to add to our Suite
Iterator testClassNames = getClassNamesViaJython()iterator()
while( testClassNameshasNext() ) {
String classname = testClassNamesnext()toString()
try {
// construct a Class object given the test case class name
Class testClass = ClassforName( classname )
// add to our suite
suiteaddTestSuite( testClass )
Systemoutprintln( Added: + classname )
}
catch( ClassNotFoundException e ) {
StringBuffer warning = new StringBuffer()
warningappend( Warning: Class )append( classname )append( not found )
Systemoutprintln( warningtoString() )
}
}
return suite;
}
在开始时要保证设置了正确的系统属性在内部Jython 将使用 pythonhome 属性来定位它所需要的文件最终会调用 getClassNamesViaJython() 方法在这里面会有一些奇妙的事情发生如在清单 中将会看到的
清单 从 Python 运行时提取 Java 对象
/**
* Get list of tests were going to add to our suite
* @return List A List of String objects each representing class name of a TestCase
*/
private List getClassNamesViaJython() {
// run python script
interpreterexecfile( getPathToScript() )
// extract out Python object named PYTHON_OBJECT_NAME
PyObject allTestsAsPythonObject = interpreterget( PYTHON_OBJECT_NAME )
// convert the Python object to a String[]
String[] allTests = (String[]) allTestsAsPythonObject__tojava__( String[]class )
// add all elements of array to a List
List testList = new ArrayList()
testListaddAll( ArraysasList( allTests ) )
return testList;
}
首先对 Python 文件进行判断然后从 Python 运行时提取出一个 PyObject 这就是得到的对象它包含将构成测试包的所有测试用例的类名(记住 PyObject 是 Python 对象的 Java 运行时对应物)然后创建具体的 List 并用 PyObject 填充它使用 __tojava__ 指示 PyObject 将其内容转换为一个 Java String 数组最后将控制返回 getTestSuite() 在这里装载 Jython 标识的测试用例并将它们添加到组合包(composite)中
在Jython开发环境中安装测试装具模块
现在对于测试装具模块如何工作已经有了很好的认识可能迫不及待要自己试试它了您将需要完成以下步骤以配置 Eclipse 来运行这个装具模块(如果使用不同的 IDE应当可以容易地针对您的环境修改这些步骤)
安装 Jython 如果还没安装的话(链接请见 参考资料)
拷贝 getalltestspy 到主目录
编辑 getalltestspy 第 行以指定到源文件的根路径会搜索在这个位置下的所有目录中与 org 包中 *Textjava 匹配的文件名
如果有必要修改第 行以改变根包名(例如改为 com)
将 DynamicTestSuitejava 拷贝到源树中
将以下 JAR 添加到 Eclipse 项目中
junitjar (JUnit 框架二进制文件下载信息请参阅 JUnit 的 Web 网站)
jythonjar(Jython 二进制文件位于 Jython 安装目录)
将 DynamicTestSuite 类装载到 Eclipse Java 源文件编辑器中执行以下步骤之一
在 Package Explorer 视图中选择 DynamicTestSuite 或者
按 Ctrl+Shift+T并在 Choose Type 输入字段键入 DynamicTestSuite
从文件菜单栏选择 Run然后选择 Debug…
选择 JUnit配置
单击 New按钮将会创建一个新的 JUnit 目标 DynamicTestSuite 应当预填入 Test Class 字段
选择 Arguments选项卡
在 VM 参数文本框中键入 Dpythonhome=<path where you installed Jython>
单击 Debug按钮
变!现在就有了一个具体的 JUnit TestCase 类可以像静态定义的包那样处理它设置边界并进行调试!不需要修改 Test 类装具模块将构建一个包就像您显式将每一个 Class 对象编写到包中一样如要执行测试可以通过喜爱的调试器编译工具(如 Ant 或 CruiseControl)或者一个 JUnit 内含的 test runner 调用这个装具模块
扩展这个装具模块
我相信您注意到了除非在运行前修改源代码否则这个装具模块只能用于一个项目可以容易地扩展这个装具模块让它支持多个项目一种简单的方式是修改 getPathToScript() 以使用指定特定于项目的属性的系统属性