JaVa
   

Why In-Container Testing?

You need a framework like Cactus to test J2EE code. Why? Because of the special relationship that J2EE code—servlets, JSPs, EJBs, and so on—holds with its container. Naturally, unit testing focuses on units of program code. However, no code exists in a vacuum. Even the simplest program is dependent on other units of code (any Java program, for instance, depends on the JVM). One of the biggest challenges of unit testing is how to "tease" the unit apart from its context so that its behavior can be asserted independently.

Testing in Isolation: Mock Objects

The idea behind mock object testing, or endo-testing, is to define "mock objects" that test cases can pass to the code being exercised. These mock objects take the place of domain objects and have dummy behavior that test cases can configure at runtime. A typical mock object interaction might go something like this:

public void testFilter() {
 mockFilter.setExpectedFilterCalls(1);
 filterableObject.apply(mockFilter);
 mockFilter.verify();
}


The verify() method would ensure that the expected calls to the filter's filter() method occurred. Endo-testing solves the problem of code in context by providing an ultra-lightweight emulation of that context on a per-test basis. The mock object passed to code under test should encompass all of the state that the code needs to operate correctly.

Advantages to Mock Objects

Using mock objects refines the practice of unit testing by ensuring almost totally independent operation of the code under test. This provides a variety of advantages. Certain app states may be impossible, difficult, or time consuming to reproduce. Mock objects avoid this problem by keeping all of the "state" set up in the mock object. Mock objects also improve code design, according to the authors of Endo-Testing: Unit Testing with Mock Objects (a paper presented by Tim Mackinnon, Steve Freeman, and Philip Craig at "eXtreme Programming and Flexible Processes in Software Engineering—XP2000"). An isolating unit test "improves domain code by preserving encapsulation, reducing global dependencies, and clarifying the interactions between classes."

Disadvantages of Using Mock Objects

Mock objects require some effort to design and implement. Mackinnon et al. point to this difficulty: "In some cases it can be hard to create Mock Objects to represent types in a complex external library. The most difficult aspect is usually the discovery of values and structures for parameters that are passed into the domain code." In the case of J2EE container services, the complexity and scope of the libraries can be very high. With time, as the Java community develops reusable mock libraries and perfects code- generation tools for mock objects, this disadvantage will lessen. Several projects dedicated to these goals already exist, such as EasyMock (http://www.easymock.org/) and DynaMock. DynaMock is part of the Mock Objects Framework (http://www.mockobjects.com), an open source project with contributors from the Cactus team. Another promising tool is MockMaker (http://www.mockmaker.org/), which has plug-ins for most popular IDEs, including Eclipse and JBuilder.

Testing in Context: Integration Testing

Integration, or in-container, J2EE testing eliminates the problem of isolating tests by embracing it. Integration tests attempt to test the domain code (as far as is possible) from within the context provided by the container. For instance, an in-container test for a servlet would exercise its doGet() method by passing it actual or thinly wrapped versions of real ServletRequest and ServletResponse objects.

Advantages to Integration Testing

The chief advantage to in-container testing is verification of the interaction between domain code and container services. J2EE containers grow in complexity and scope every year, providing such diverse services as object life-cycle maintenance, security, transactions, object persistence, and so on. Containers rely on declarative deployment descriptors to specify and configure many of these services. Although many J2EE services are governed by the specifications released through the Java Community Process, the specifications leave many implementation details to container providers. Furthermore, there are no guarantees that a given container will be bug-free or that it will implement the specification exactly.

Java Start Sidebar
A Real-World example

I worked on a project where we wanted to take advantage of some new features of the EJB 2.0 specification. We were working with a small app server (Resin), which had recently implemented the parts of the specification (specifically container-managed persistence) that we were interested in. Using integration tests helped us refine our understanding of the domain because we had to precisely specify our expectations about the container, and it helped us uncovered some pitfalls early. We were getting an intermittent error that showed up in about one out of three test runs.

It turned out that the service we were requesting needed to be isolated in a transaction to avoid modification to underlying database tables while the service was running. Integration tests helped us focus our attention on the specific container service that we were calling-—without the surrounding context of an app to distract us.

Java End Sidebar

Integration testing allows developers to verify all the aspects of testing that elude verification in the domain code. Proper in-container tests can validate assumptions about the way the app is configured (is such-and-such a servlet mapped correctly?)and whether services perform as expected, and helps track down bugs that result from component interaction.

Disadvantages of Using Integration Testing

By its nature, integration testing is less "unit" than mock object testing. Although it helps verify interaction with the container, it does not provide especially good verification of domain code. Integration testing generates less pressure to refine the underlying design, since integration tests work perfectly well with systems of interdependent components. On the flip side, the context that in-container tests provide cannot be exactly identical to the code's production context. Integration testing is white-box testing—it gets inside the container to test pieces of logic. The intrusion of test code into the container must necessarily alter the surrounding context. As an example, Cactus provides wrappers for several container objects. Although the extra behavior added by these wrappers should not affect testing adversely, it could cause results that would not occur in a normal context. Black-box testing (with a framework such as HttpUnit) that calls an app externally can replicate a production environment more closely.

A Blend of Approaches

Ideally a full test suite would include verification of the domain logic with mock objects, integration testing with Cactus, and functional tests with a framework such as HttpUnit. However, integration testing is a critical part of this picture. J2EE apps rely can on container services so heavily that not having at least a "smoke test" of interaction with the deployment container amounts to a significant project risk.


JaVa
Comments