by Charles Lerose

Java Tip 73: Test your Java/CORBA server from the inside

how-to
May 1, 199910 mins

Learn how to test your server classes from within the running server by dynamically loading and executing test classes inside the server's virtual machine

Testing complex server code can be a difficult task. Often the code you want to test depends heavily on other parts of the system. It can be torturous to try to isolate that piece in order to really exercise it well. Often the code you want to test is not even directly reachable from the client side. By executing your test within the same virtual machine in which the server class executes, you can enjoy the fullest access to the features of that server class. You could instantiate the server class in a freestanding test program, but this approach may not provide the same context the server class would encounter when it runs within the server. It would be nice if you could test such classes in the context in which they normally reside: the running server.

When we write our server code in Java, we have easy access to dynamic loading on a very granular scale. This dynamic loading is the key to a solution for our testing challenge. A few lines of extra code in the server enables thorough testing of the server while the server is running. A test sequence we wish to perform can be placed in a test class which the server can be directed to dynamically load and execute by name.

Figure 1 shows a set of objects within a CORBA server. The lollipop or jack is a simple way to denote an interface — a set of related operations. The arrow flowing into the jack shows usage of that interface. Imagine an object on the server side that is not directly exposed to the ORB. In this figure, you’ll find just such an object, an object of the CustomerStorage class. Thorough testing of the CustomerStorage class from the client side may be difficult or impossible. But to instantiate objects of the CustomerStorage class from a freestanding test program also may be undesirable due to its dependency on the Customer class.

We don’t have to live with the limits of either approach. We can test our server code in place, while the server is running! Again, the key is Java’s ability to dynamically load a class and to instantiate new objects of that class. In effect, we will inject our test code into the server. The first step is to define a Java interface that our test classes will implement. Our CORBA server will know nothing of the test classes, but it will know of this new interface. We define this interface to eliminate static dependency of the server to specific tests. We can write as many test classes as we like, without ever having to recompile the server.

To keep things simple, we define a test interface called InjectableProgram, with a single operation called start:

      interface InjectableProgram {
            void start();
      }

Each test class must implement this interface. Believe it or not, half our work is done. A test class named CustomerStorageTest that implements InjectableCode, can be loaded and executed with the following code fragment. We will use a more flexible variation if this fragment is in our server.

      Class c = Class.forName("CustomerStorageTest");
      InjectableProgram program = (InjectableProgram) c.newInstance();
      program.start();

It’s almost that simple!

The package java.lang contains a class named Class that looks a bit like this:

      package java.lang; ... public class Class { public static native 
      Class forName(String className); public native Object 
      newInstance(); ... }

Class contains a static method, forName, that will dynamically load a Java class and return an instance of the type Class that represents that loaded class. The forName method expects as a parameter a fully qualified class name, and the named class must reside in the classpath of the virtual machine in which forName is called. Bear in mind that only the first occurrence in the classpath of a class with that name will be found by forName.

We can call newInstance on the Class object returned by Class.forName() to create new objects of that named class. newInstance will only succeed if the class in question has a no-arg constructor and has the appropriate visibility to the caller. For example, if the class has only package visibility and resides in a different package than the caller, newInstance will fail.

If that loaded class implements InjectableCode, then we can successfully cast the returned instance to an InjectableCode, and invoke methods on it. This example invokes the start method. The start method could perform tests of other classes within the server. Of course, the loaded class is subject to the normal visibility restrictions of all classes in the system. For example, it cannot access methods with package visibility on classes outside its own package.

Notice that earlier I said “almost that simple.” In the example above, the class name CustomerStorageTest was hard-coded. What we really want is to select the test class at runtime. Since this is a CORBA server, why not define a new CORBA interface for injecting an arbitrary test program into the system? Let’s revisit our earlier diagram but with a few new features.

Here we have exposed a new interface, called Injector, to the ORB. This gives us a way to tell the running server which test to run, or to inject into itself. Here’s the definition of that interface in CORBA IDL.

       module Injection {
            interface Injector {                     void inject(in string className);
            };
      };

This interface provides just one operation named inject, which takes one parameter: the string name of the class to inject into the server. The implementation of inject must load the specified class, create a new object of that type, and then call the start method on the new object.

    public void inject(String className) {
        try {
            Class c = Class.forName(className);
            InjectableProgram program = (InjectableProgram) c.newInstance();
            program.start();
        } 
        catch(Exception ex) {
            ex.printStackTrace();
        }
    } 

Now it really is that simple!

Note that the methods of Class may throw exceptions that we must catch. Here’s a client code snippet to request the test.

 Injector server = InjectorHelper.bind(...);
 server.inject("CustomerStorageTest");

A generic client program could accept a command line argument that specifies the fully qualified name of the test class to inject.

It is important to understand that the client is only passing a name string to the server. The test class will be loaded by the server, not the client. That named class must reside in the classpath of the virtual machine of the server, which need not be the same as the classpath of the virtual machine of the client program. If the test class does not reside in the appropriate place in the classpath of the server, the server’s class loader will certainly not find it.

That’s all. By defining one new Java interface, one new ORB interface, and implementing that CORBA interface with a tiny method, we can now test our CORBA server from the inside.

This Class.forName() technique suffers from some limitations. After the test is loaded by Class.forName(), the test class may continue to occupy memory in the virtual machine, even after the test is complete.

(Prior to JDK 1.2, the VM was allowed to unload classes for which there were no reachable instances. Although JDK 1.0 VMs do not bother to unload classes, the JDK 1.1 VMs do. JDK 1.2 VMs will not unload a class unless the class loader that loaded that class is discarded. The point is, in JDK 1.0 and 1.2 VMs, your test classes will continue to occupy memory after the test is complete.)

Another limitation is that when a particular test is modified and recompiled, the newer version will never be loaded. (Unless the test class has been unloaded in those VMs that do so.) The previous version, still in memory, would satisfy any request to Class.forName() for that particular test class.

In his January Design Techniques column, Bill Venners describes these issues and shows how to create a custom ClassLoader to avoid both problems. The example code that accompanies this article also contains a second server that uses such a custom ClassLoader.

Example code

Linked here is the source code for two complete CORBA servers, a test class, and a simple CORBA client program to initiate the test. The two servers share some code, and the test class and the client program work with either example server. These examples have been tested with JDK 1.2 and the ORB that is part of that JDK. Some slight modifications might be necessary to use these examples with another vendor’s ORB.

To compile these programs for Sun’s ORB, you will also need the Java IDL compiler from Sun. See the resources section for a link to the Java IDL compiler.

The first example server uses the technique described in this Java Tip and is composed of the following source code:

The second example server overcomes some limitations described earlier by defining a custom ClassLoader.

There is a single test class that can be injected into either example server. I place it in a subdirectory named test:

This client program can direct either server to execute the test class:

The file called readme.txt contains instructions for building and executing the examples. For convenience, jw-javatip73.zip contains all the example files zipped together.

Conclusion

This tip has shown how to dynamically load test code into a running server to test server classes in their natural context. This is a technique that is useful for non-CORBA applications as well, including RMI servers or any large or long-running application.

You could use this technique to inject diagnostic code into a running server to track down (or even resolve!) a problem at runtime without shutting down the server.

Variations of the technique are by no means limited to the following:

  • Pass additional parameters to the inject method that could be on to the start method. (See the java.lang.reflect package for even more flexibility.)

  • Return a value from start and inject to indicate the results of the test to the client program.

  • Use dynamic loading of test classes in a GUI program, and let the user type in the name of the test class, or select from a list.

Things to remember:

  • Make sure each test class is public, so the Injector can instantiate it.

  • Each test class must have a no-arg constructor. Make this public too.

  • Each test class must reside on the classpath of the server.

Restrictions on the tests:

  • The test classes are subject to the normal visibility rules imposed on all Java classes. For example, they can’t call package visible methods on classes outside their own package. (Technically, you are free to place a test class in the package of the classes it will test, so this limitation is not too great.)

  • Once loaded, a test class usually stays loaded, and so continues to occupy memory and cannot be replaced without shutting down the server. (See Bill Venners’s column and my example #2 to get around these problems.)
Charles LeRose is a consultant with Greenbrier and Russel (http://www.gr.com). He has been developing software professionally for twelve years and typically builds distributed applications using tools like Java, C++, and CORBA, or whatever is handy. Based in Boulder and a recent transplant to Colorado, Charles has been recently seen flailing his arms about on many ski slopes in the mountains of that lovely state.