 | Level: Intermediate 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.
Download | Name | Size | Download method |
|---|
| j-jythtest-source.jar | | HTTP |
Resources - Download the source code used in this article.
- If you're new to Jython, you won't want to miss Barry Feigenbaum's comprehensive, two-part tutorial, "Introduction to Jython" (developerWorks, April 2004).
- Visit Jython's home page for binaries, source, and documentation.
- See JUnit's Web site for additional information on JUnit and unit testing.
- JUnit: A Cook's Tour is an in-depth introduction to JUnit.
- In Matt Chapman's comprehensive introduction to Ant, "Apache Ant 101: Make Java builds a snap" (December 2003), you'll walk through the steps involved in writing a build file for a simple Java project, and then look at some of Ant's other useful functions, including filesystem operations and pattern matching. You'll finish the course by writing your own Java class that extends Ant's functionality.
- developerWorks features a variety of articles and tutorials on JUnit and unit testing in general, including:
- Eric Allen's Diagnosing Java code column on developerWorks frequently discussed ways in which unit testing can aid software development. Browse the column's archives to find out more.
- Eclipse is an open source development platform.
- View a how-to article on "Debugging with the Eclipse Platform," Pawel Leszek (developerWorks, May 2003).
- Ant is a great way to create flexible and portable build scripts.
- The "WebSphere Version 4 Application Development Handbook" contains a section on automated unit testing with JUnit.
- Browse for books on these and other technical topics.
- You'll find articles about every aspect of Java programming in the developerWorks Java technology zone.
About the author  | 
|  | Michael Nadel has been professionally developing systems for four years. During his tenure, Michael has consulted for dot-coms and global financial institutions. Michael's expertise and passion lies with Java technology and object-oriented systems. You can contact him at mnadel@flywheelcorporation.com |
Rate this page
|  |