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:
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:
  1. Extend a class from ServerTestCase and override its setUpCORBA() method.
  2. 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.)
  3. 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:
  1. Extend from ClientTestCase.
  2. Declare a private variable to hold a reference to the CORBA object used for testing (EventTarget in this example).
  3. Override the setUp() method.
  4. 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.
  5. Call the parent's setUp() method so that it can initialize CORBA.
  6. 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().
  7. 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.