Java开发网 Java开发网
注册 | 登录 | 帮助 | 搜索 | 排行榜 | 发帖统计  

您没有登录

» Java开发网 » 技术文章库  

按打印兼容模式打印这个话题 打印话题    把这个话题寄给朋友 寄给朋友    该主题的所有更新都将Email到你的邮箱 订阅主题
flat modethreaded modego to previous topicgo to next topicgo to back
作者 用 Jython 构建 JUnit 测试包--摘自IBM DeveloperWorks
palatum



CJSDN高级会员


发贴: 451
积分: 80
于 2004-06-06 14:30 user profilesend a private message to usersend email to palatumsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
用 Jython 构建 JUnit 测试包

Python 和 Java 技术共同工作以完成不可能的任务

Michael Nadel (mnadel@flywheelcorporation.com)
Java 开发员, Chicago Technology Partners
2004 年 5 月

开发人员有多种理由决定自动化单元测试。许多人甚至进一步发挥它,自动化这些测试的定位和执行。但是如果想要测试装具模块(test harness)像静态定义的那样运行呢?请跟随开发员 Michael Nadel,看看如何利用 Python 模拟静态定义的 JUnit TestSuite 类。
JUnit 测试框架被越来越多的开发小组所共同使用。归功于各种各样的测试装具模块,现在可以测试构成任何 Java 应用程序的几乎每一个组件。事实上,几乎整个二级市场似乎都是用围绕 Junit 建立的。包括 Cactus、jfcUnit、XMLUnit、DbUnit 和 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 的教程,不过还是看下面这两行代码作为复习。清单 1 是静态构建 TestSuite 类的一个例子(这个例子取自 JUnit: A Cook's Tour,有关它和其他 JUnit 资源的链接请参阅 参考资料):

清单 1.静态定义 TestSuite

public static Test suite() {
return new TestSuite( MoneyTest.class );
}



清单 1 表明 TestSuite 是由 Test 类的类实例组成的。这个装具模块完全利用了这一点。为了分析这个工具的代码,应从 参考资料 中下载本文的示例 JAR 文件。这个文档包含两个文件:DynamicTestSuite.java 和 getalltests.py,前者是一个用 Phthon 脚本动态生成 TestSuite 的 JUnit 测试装具模块,后者是一个搜索匹配特定样式的文件的 Python 脚本。DynamicTestSuite.java 使用 getalltests.py 构建 TestSuite。可以修改 getalltests.py 以更好地适合自己的项目的需要。

了解测试装具模块
代码是如何工作的?首先,指派 getalltests.py 获取一组要执行的 Test 类。然后,使用 Jython API 将这个列表从 Python 运行时环境中提取出来。然后使用 Java Reflection API 构建在表示 Test 类名的列表中的 String 对象的类实例。最后,用 JUnit API 将 Test 添加到 TestSuite 中。这四个库的相互配合可以实现您的目标:动态构建的 TestSuite 可以像静态定义的那样运行。

看一下清单 2 中的 JUnit suite 清单。它是一个公开 public static TestSuite suite() 方法签名的 TestCase。由 JUnit 框架调用的 suite() 方法调用 getTestSuite(), getTestSuite() 又调用 getClassNamesViaJython() 以获取一组 String 对象,其中每一个对象表示一个作为包的一部分的 TestCase 类。

清单 2. 动态定义 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 we're going to add to our Suite
Iterator testClassNames = getClassNamesViaJython().iterator();

while( testClassNames.hasNext() ) {
String classname = testClassNames.next().toString();

try {
// construct a Class object given the test case class name
Class testClass = Class.forName( classname );

// add to our suite
suite.addTestSuite( testClass );

System.out.println( "Added: " + classname );
}
catch( ClassNotFoundException e ) {
StringBuffer warning = new StringBuffer();
warning.append( "Warning: Class '" ).append( classname ).append( "' not found." );
System.out.println( warning.toString() );
}
}

return suite;
}



在开始时,要保证设置了正确的系统属性。在内部,Jython 将使用 python.home 属性来定位它所需要的文件。最终会调用 getClassNamesViaJython() 方法,在这里面会有一些奇妙的事情发生,如在清单 3 中将会看到的。

清单 3. 从 Python 运行时提取 Java 对象

/**
* Get list of tests we're 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
interpreter.execfile( getPathToScript() );

// extract out Python object named PYTHON_OBJECT_NAME
PyObject allTestsAsPythonObject = interpreter.get( 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();
testList.addAll( Arrays.asList( allTests ) );

return testList;
}



首先,对 Python 文件进行判断。然后,从 Python 运行时提取出一个 PyObject。这就是得到的对象,它包含将构成测试包的所有测试用例的类名(记住 —— PyObject 是 Python 对象的 Java 运行时对应物)。然后创建具体的 List 并用 PyObject 填充它,使用 __tojava__ 指示 PyObject 将其内容转换为一个 Java String 数组。最后,将控制返回 getTestSuite(),在这里装载 Jython 标识的测试用例,并将它们添加到组合包(composite)中。

在开发环境中安装测试装具模块
现在对于测试装具模块如何工作已经有了很好的认识,可能迫不及待要自己试试它了。您将需要完成以下步骤以配置 Eclipse 来运行这个装具模块。(如果使用不同的 IDE,应当可以容易地针对您的环境修改这些步骤。)

安装 Jython 2.1,如果还没安装的话。(链接请见 参考资料 )。

拷贝 getalltests.py 到主目录。

编辑 getalltests.py 第 25 行以指定到源文件的根路径,会搜索在这个位置下的所有目录中与 org 包中 *Text.java 匹配的文件名。
如果有必要,修改第 54 行以改变根包名(例如,改为 com)。

将 DynamicTestSuite.java 拷贝到源树中。

将以下 JAR 添加到 Eclipse 项目中:
junit.jar (JUnit 框架二进制文件,下载信息请参阅 JUnit 的 Web 网站)。
jython.jar(Jython 二进制文件,位于 Jython 安装目录)。

将 DynamicTestSuite 类装载到 Eclipse Java 源文件编辑器中。执行以下步骤之一:
在 Package Explorer 视图中选择 DynamicTestSuite,或者
按 Ctrl+Shift+T 并在 Choose Type 输入字段键入 DynamicTestSuite。

从文件菜单栏选择 Run,然后选择 Debug...。

选择 JUnit 配置。

单击 New 按钮。将会创建一个新的 JUnit 目标,DynamicTestSuite 应当预填入 Test Class 字段。

选择 Arguments 选项卡。

在 VM 参数文本框中键入 -Dpython.home=<path where you installed Jython>。

单击 Debug 按钮。
变!现在就有了一个具体的 JUnit TestCase 类,可以像静态定义的包那样处理它。设置边界并进行调试!不需要修改 Test 类,装具模块将构建一个包,就像您显式将每一个 Class 对象编写到包中一样。如要执行测试,可以通过喜爱的调试器、编译工具(如 Ant 或 CruiseControl),或者一个 JUnit 内含的 test runner 调用这个装具模块。

扩展这个装具模块
我相信您注意到了除非在运行前修改源代码,否则这个装具模块只能用于一个项目。可以容易地扩展这个装具模块让它支持多个项目。一种简单的方式是修改 getPathToScript() 以使用指定特定于项目的属性的系统属性。可以在自己的项目中自由使用它,可以不加改变地使用它,也可以以它为基础进行加工。不过,请别忘记它的 GPL 许可证。



作者 Use Jython to build JUnit test suites(英文原文) [Re:palatum]
palatum



CJSDN高级会员


发贴: 451
积分: 80
于 2004-06-06 14:32 user profilesend a private message to usersend email to palatumsearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
Use Jython to build JUnit test suites

Python and Java technology work together to solve the impossible

Michael Nadel (mnadel@flywheelcorporation.com)
Java Developer, Chicago Technology Partners
11 May 2004

Developers decide to automate unit tests for a number of reasons. Many take it even a step further and automate the location and execution of those tests. But what if you need your test harness to act as if it were statically defined? Follow along with developer Michael Nadel and see how to use Python to feign statically defined JUnit TestSuite classes.
The JUnit testing framework is commonly used by an increasing number of development teams. Thanks to a myriad of testing harnesses, it's now possible to test almost every component comprising any type of Java application. In fact, it's almost as if an entire secondary market is forming around JUnit. Harnesses including Cactus, jfcUnit, XMLUnit, DbUnit, and HttpUnit are all freely available to developers for use in testing our applications. As systems' complexities increase, and with so many tools at our disposal, there's little reason to not rely on unit tests.

However, developers are more than just programmers. We interact with users to fix bugs and hammer out requirements. We go to meetings and go on sales calls. We perform some (and sometimes all) of the functions of quality assurance. With so many responsibilities, it is only natural to want to automate as much as possible. Because great teams (among other things) generate a lot of tests, this is an area that often comes under the scrutiny of those who seek to automate various development processes.

Automating unit tests
There are many ways to automate the location and execution of all a project's test cases. One solution is to use Ant's junit task in conjunction with a nested fileset task. This allows you to include and exclude files (based on filename patterns) under a specified directory. Another option is to use a feature of Eclipse with which you can specify a directory under which all tests are located and executed. The former option provides flexibility in filtering the tests that are run (and since it's a pure headless Java application, it can be run almost anywhere), and the latter option allows you to debug your "dynamic" suite. But can you combine the power and flexibility of these two approaches?

Thanks to Jython, a Java platform implementation of the Python programming language, the answer is a resounding "yes!" (If you're not familiar with Jython, you should brush up before going further in this article; see the links in the Resources section below for more information.) Using the power and elegance of Jython, you can maintain a script that scours your filesystem, searching for classes that match a certain pattern, and dynamically build a JUnit TestSuite class. This TestSuite class, like any other statically defined class, can be easily debugged using your favorite debugger. (The examples I'll use in this article will assume use of the Eclipse IDE; however, the techniques I describe here will work with most other IDEs without much modification.)

When making any design decisions, you must address the tradeoffs of your options and the impact of your decision. In this case, in order to gain the ability to debug dynamically generated test suites, you are forced to add additional complexity. The complexity, however, is mitigated by Jython itself: Jython is well tested and supported, and is open source. Furthermore, Python is increasingly becoming the de facto standard of object-oriented, platform-independent scripting. For these two reasons, there is little risk in adopting Jython, especially given the payoff: unparalleled flexibility in the creation and debugging of dynamically generated JUnit TestSuite classes.

If adopting Jython were a major concern, it'd be possible to nevertheless make headway on the original problem without it. Instead of using Jython, you could use a Java Property file to store a list of classes, directories, and packages to include or exclude tests from your suite. However, in choosing to use Jython, you are able to bring to bear the entire Python language and runtime against your problem of choosing which tests to execute. A Python script is so much more flexible than Java Property file that the possibilities are limited only by your imagination.

Exploiting Jython's seamless integration with the Java platform allows you to create a statically defined, yet dynamically constructed TestSuite class. Numerous tutorials on JUnit exist, but let's look at a two-line refresher. Listing 1 is an example of how TestSuite classes are statically constructed (this example comes from JUnit: A Cook's Tour; see Resources for a link to this and other JUnit resources):

Listing 1. Statically defining a TestSuite

public static Test suite() {
return new TestSuite( MoneyTest.class );
}



Listing 1 illustrates that a TestSuite is composed of class instances of Test classes. The harness fully takes advantage of this. To take a look at the harness code, download this article's sample JAR file from the Resources section. This archive contains two files: DynamicTestSuite.java, a JUnit test harness that dynamically generates a TestSuite using a Python script; and getalltests.py, a Python script that searches for files matching a specific pattern. DynamicTestSuite.java uses getalltests.py to build a TestSuite. You can modify getalltests.py to better match your project's needs.

A look at the test harness
How does the code work? First, you delegate to getalltests.py to retrieve a list of Test classes to execute. Next, you use the Jython API to extract that list out of the Python runtime environment. Then you use the Java Reflection API to construct class instances of the String objects in your list that represent Test class names. Finally, you turn to the JUnit API to add the Test to your TestSuite. It is the interoperation of these four libraries that allows you to achieve your goal: a dynamically constructed TestSuite that acts as if it were statically defined.

Take a look at the JUnit suite listing in Listing 2. It's a TestCase that exposes the public static TestSuite suite() method signature. The suite() method, which is called by the JUnit framework, calls getTestSuite(), which in turn calls getClassNamesViaJython() in order to retrieve a list of String objects, each representing a TestCase class that is part of the suite.

Listing 2. Dynamic defining a 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 we're going to add to our Suite
Iterator testClassNames = getClassNamesViaJython().iterator();

while( testClassNames.hasNext() ) {
String classname = testClassNames.next().toString();

try {
// construct a Class object given the test case class name
Class testClass = Class.forName( classname );

// add to our suite
suite.addTestSuite( testClass );

System.out.println( "Added: " + classname );
}
catch( ClassNotFoundException e ) {
StringBuffer warning = new StringBuffer();
warning.append( "Warning: Class '" ).append( classname ).append( "' not found." );
System.out.println( warning.toString() );
}
}

return suite;
}



At the outset, you ensure that the correct system property is set. Internally, Jython will use the python.home property to locate its required files. Eventually, the getClassNamesViaJython() method is invoked, which is where all the magic happens, as you'll see in Listing 3.

Listing 3. Extracting Java objects of the Python runtime

/**
* Get list of tests we're 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
interpreter.execfile( getPathToScript() );

// extract out Python object named PYTHON_OBJECT_NAME
PyObject allTestsAsPythonObject = interpreter.get( 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();
testList.addAll( Arrays.asList( allTests ) );

return testList;
}



First, the Python file is evaluated. Next, you extract a PyObject out of the Python runtime. This is the resulting object that contains the class names of all the test cases that will compose your suite. (Remember -- a PyObject is the Java runtime counterpart to a Python object.) Then you create a concrete List and populate it with the contents of the PyObject, using __tojava__ to instruct the PyObject to convert its contents to a Java String array. Finally, control is returned to getTestSuite(), where you load the test cases that Jython identified, and add them to the composite.

Install the test harness in your development environment
Now that you have a solid understanding of how the test harness works, you're probably eager to try it out yourself. You'll need to walk through the following steps to configure Eclipse to run the harness. (If you're using a different IDE, you should be able to easily adapt these steps for your environment.)

Install Jython 2.1 if you haven't already (see Resources for a link).

Copy getalltests.py to your home directory.

Edit line 25 of getalltests.py to specify the path to the root of your source; all directories under this location will be searched for filenames matching *Test.java within the org package.
If necessary, modify line 54 to change the root package name (to com, for example).

Copy DynamicTestSuite.java into your source tree.

Add the following JARs to your Eclipse project:
junit.jar (the JUnit framework binaries; see JUnit's Web site for download information).
jython.jar(Jython binaries; located in the Jython installation directory).

Load the DynamicTestSuite class into Eclipse's Java source editor. Follow one of the steps below:
Select DynamicTestSuite from the Package Explorer view, or;
Press Ctrl+Shift+T and type DynamicTestSuite into the Choose Type input field.

Select Run from the file menu bar, then Debug...

Select the JUnit configuration.

Click the New button. A new JUnit target will be created, and DynamicTestSuite should be pre-filled in the Test Class field.

Select the Arguments tab.

Type -Dpython.home=<path where you installed Jython> into the VM arguments text box.

Click the Debug button.
And presto! You now have a concrete JUnit TestCase class that can be treated as if the suite composite were statically defined. Set your breakpoints, and debug away! No modifications to your Test classes are needed; the harness constructs a suite as if you had explicitly coded each Class object into the suite. In order to execute your tests, the harness can be invoked through your favorite debugger, build tool (such as Ant or CruiseControl), or one of JUnit's included test runners.

Extending the harness
I'm sure you noticed that the harness will work only for a single project, unless you were to modify the source before running it. You could easily extend the harness to support multiple projects. One simple way is to modify getPathToScript() to use system properties that specify project-specific attributes. Feel free to use, either as-is or as a foundation, the harness in your own projects. Please, however, be mindful of its GPL license.




flat modethreaded modego to previous topicgo to next topicgo to back
  已读帖子
  新的帖子
  被删除的帖子
Jump to the top of page

   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923