How to Create JUnit Tests for Zen
Author: Trevor Harmon
Date: August 22, 2003
I'm sure you already know how important JUnit tests are in the
development of Zen. If not, jump to this
page immediately or visit the JUnit
home page. The bottom line is, if we don't have a test case for any
given feature of Zen, then we must
assume that it doesn't work. And besides, it's not enough simply to
tell our overseers (Boeing, for example) that our software works. We
have to show them that it works.
All of this means that our goal should be to create as many tests as
possible. Although JUnit goes a long way toward simplifying this
process, it offers no support for the client-server model of CORBA,
and, based on my experience, its handling of multi-threaded test cases
is broken. Plus, once you've struggled to get JUnit working with a
single test case for Zen, you still have to cut-and-paste large chunks
of initialization and threading code for each new test case. As a
result, only two of the twenty or so tests currently in our source code
tree actually use JUnit. The rest simply dump their results to the
console, making automated testing impossible.
To solve these problems, I've developed a pair of wrapper classes that
sit on top of JUnit and simplify its interface. In the paragraphs that
follow, I'll explain how to use these classes and show you some example
code. By the time we're finished, you'll be able to create JUnit tests
for Zen more quickly and easily than ever before.
Note that this tutorial assumes you
are already familiar with JUnit; however, the classes simplify JUnit so
much that you really don't need to know much about it in order to use
them.
The JUnit Wrapper Classes for Zen
The two wrapper classes are located in the test.framework package and are
called ClientTestCase and ServerTestCase. They handle:
- Initialization of CORBA
- Registration of servants
- Binding and unbinding of servants in the name service
- Shutting down CORBA cleanly
Note that the servants are registered using the CORBA Naming Service,
so you must have Zen's naming service up and running if you want to use
these classes. (There should be a naming service running 24 hours a day
on the DOC server, so this shouldn't be a problem.)
To use the classes, all you need to do is extend from them and add the
test-specific code to your subclasses. You don't need to worry about
calling ORB.init, launching threads, or any other tedious tasks like
that. You just compile these subclasses and run JUnit on them. Easy!
Let's look at an example to see how it all works.
A Simple Example
Let's look at a real test case from Zen: the collocation test in the test.collocation package. It
consists of five files:
Let's look at the salient parts of these files. Here is CollocationTestServer.java:
package test.collocation;
import test.framework.*;
public class CollocationTestServer extends ServerTestCase
{
protected void setUpCORBA() throws Exception
{
super.setUpCORBA();
bind(new EventTargetImpl());
}
}
See how nice and short that is? You hardly have to do any work to set
up the server. Just three simple steps:
- Extend a class from ServerTestCase
and override its setUpCORBA()
method.
- Call the parent's setUpCORBA()
method to have it initialize CORBA. (Note that we declare this method
as throwing Exception so that
JUnit can catch any possible exceptions that might occur during the
test.)
- Create a new server-side instance of the CORBA object (EventTarget in this case) and
register it with Zen by calling bind().
Simple!
Now let's look at CollocationTest.java:
package test.collocation;
import test.framework.*;
public class CollocationTest extends ClientTestCase
{
private EventTarget target;
protected void setUp() throws Exception
{
server = new CollocationTestServer();
super.setUp();
target = EventTargetHelper.narrow(getServerObject());
}
public void testCollocation() throws Exception
{
// Actual testing code removed for brevity
}
}
Although the client side is a little more complex than the server side,
the steps are not difficult. Here is what this class does:
- Extend from ClientTestCase.
- Declare a private variable to hold a reference to the CORBA
object used for testing (EventTarget
in this example).
- Override the setUp()
method.
- Initialize the server
variable, which is delcared in ClientTestCase
and is of type ServerTestCase.
This is necessary so that the client can launch the server object in a
separate thread.
- Call the parent's setUp()
method so that it can initialize CORBA.
- Initialize the reference to the CORBA object on the server. The getServerObject() helper function is
used for this task; it returns an org.omg.CORBA.Object
which must then be cast to the actual object reference by calling narrow().
- Implement the test case by calling assertTrue(), assertEquals(), or any of the other junit.framework.TestCase methods.
The actual testing code for this test case was removed to keep the
listing small.
As with the server class, all methods throw Exceptions. Not only does this allow
JUnit to catch any and all exceptions, but it also simplifies your code
because you don't have to worry about catching unexpected exceptions
yourself and notifying JUnit of the problem. It's all automatic.
Note, too, that there is no constructor for the client class. All
construction of the test case is handled by JUnit. It will look for and
run any methods that are declared public
void with no parameters and have a name beginning with "test".
Thus, if you wanted to test a different part of the CORBA object, you
would just add a method called testCollocation2(),
for example, and add more calls to assert*() methods.
To build the test case, run "ant".
When it compiles successfully, run "ant
test" to have JUnit load the test case and run it. The results
of the test will appear on the console.
A More Complex Example
You may have noticed in the example above that there is only one
interface in the CORBA object. If you have a test case that requires
multiple interfaces, you will need to make a few small modifications to
your client and server initialization code. For example, let's say you
have an interface definition like this:
module test
{
module mytest
{
interface Vehicle
{
// Definition removed for brevity
};
interface Car : Vehicle
{
// Definition removed for brevity
};
};
};
In your server, a single call to bind()
is not enough. You will need to call bind()
for each interface that you want to register, passing a name as the
second parameter. It doesn't matter what name you specify as long as it
uniquely identifies the interface. For example, on the server side you
might do something like this:
bind(new VehicleImpl(), "vehicle");
bind(new CarImpl(), "car");
Then, on the client side, when you need to get a reference to these
implementations, you will need to specify the same names again, like
this:
vehicle = VehicleHelper.narrow(getServerObject("vehicle"));
car = CarHelper.narrow(getServerObject("car"));
Note that this code is exactly the same as in the first example; you
simply add the string name as a parameter.
For a complete example of how this works, refer to the test.exception test case in the Zen
source tree. The files are build.xml, exception.idl,
CarImpl.java,
VehicleImpl.java,
ExceptionTest.java,
and ExceptionTestServer.java.
Some Useful Templates
Rather than forcing you to download these examples, trim them up, and
then add your own test case code, I've done the trimming for you. All
you have to do is unpack this
archive
into a directory and replace "FooBar" with the name of your test case,
then add your test-specific code. You can also view the individual
files here:
N.B. The build file assumes that you have placed it into the Zen source
tree under Zen/packages/tests/test/foobar.
JavaDocs
Details about each method and field in the wrapper classes are
available
here:
Limitations
The JUnit wrapper classes described here were designed for basic CORBA
tests in which the client and server can both run within the same Java
Virtual Machine. It cannot handle test cases that must be run in
separate JVMs or on separate machines.
Acknowledgements
Thanks to Spart and Bruce for their help in debugging these JUnit
wrapper classes.