Newsletters Subscriptions Forums Media Kit About Us Contact Search Home

Stuff
OS/400 Edition
Volume 2, Number 22 -- November 6, 2003

JUnit Automates Java Testing


by David Morris

It doesn't matter how much time you put into design and how careful you are when programming; mistakes are inevitable. Without automated testing, it is time consuming and difficult to ensure that changes will not break existing code. Fortunately, for Java programmers, JUnit makes such testing easy. JUnit makes it fast and easy to set up unit tests for your programs. There are other reasons to consider JUnit, as well: it's free, and support is built into tools like Eclipse and WDSc.

Unit tests are the building blocks of testing. A unit test ensures that individual software components produce expected results, thus fulfilling their contract. Regression testing combines unit tests to verify that software components work together correctly after changes are made. If tests are difficult and time-consuming to write, they won't get written. The JUnit testing framework makes it easy to create unit tests and to perform regression testing.

Quite often, developers test after the coding is completed and just before the code is moved to production. Because of time constraints, a few spot checks are completed and the real testing begins when a system is turned over to a quality assurance team or when the code is moved to production. At that point, developers are in a reactionary mode and have little time to make design changes.

Many programmers find that developing tests early in the development process also helps them to build a better understanding of how classes may interact with other classes. This results in better interfaces and less rewriting. Unit tests also help you to understand how the individual pieces that form an application work and perform, before the final assembly process begins. Programmers maintaining an application can also run unit tests to understand how a particular method or class works.

Avoiding Failure

A lot of the unit tests I write fail the first time I run them. That's okay, though, because the cost of fixing a bug early in the development cycle is low. Unit testing during development helps to point out interface flaws. Often, I will adjust the interface because a method is more difficult to work with than I anticipated.

A while back, in the Midrange Guru newsletter, I responded to a reader's question about comparing objects in Java. That reader was wondering if there was a way to get Java to be less stringent when testing the equality of string values. I provided a Java class that supported loose comparison of Java strings. That class is a candidate for unit testing. To help you understand how JUnit works, I will walk you through the process of building a unit test for the LooslyComparableString class.

The LooslyComparableString class is a wrapper for the String class that ignores leading and trailing white space, along with case, when performing equals comparisons. Equal objects should always return an equal hash code. Two Java objects that compare as equals should always return the same hash code. A hash code is a short value that represents, as uniquely as practical, the contents of a Java object. Here is how I coded the hashCode() method LooslyComparableString.

public int hashCode() {
    return this.comparableString.trim().toLowerCase().hashCode();
} 

The following JUnit test shows how I tested to make sure that the hash code returned from different case strings with different white space returned the same hash code.

package demo;

import junit.framework.TestCase;
import junit.textui.TestRunner;

public class LooselyComparableStringTest extends TestCase {

 public static void main(String[] args) {
     TestRunner.run(LooselyComparableStringTest.class);
 }

 public void testHashCode() {
  LooselyComparableString lcs1 = new LooselyComparableString(" Abc ");
  LooselyComparableString lcs2 = new LooselyComparableString("  abc");
  LooselyComparableString lcs3 = new LooselyComparableString("ABC  ");
  assertEquals(lcs1.hashCode(), lcs2.hashCode());
  assertEquals(lcs1.hashCode(), lcs3.hashCode());
  assertEquals(lcs2.hashCode(), lcs3.hashCode());
 }
}

I ran this test by calling the LooslyComparableStringTest main method, and the results looked like this:

.
Time: 0.01

OK (1 test)

In this case, the test worked. Now I will show you what happens when the code doesn't work. Let's say I implemented the hashCode() method like this:

public int hashCode() {
    return this.comparableString.toLowerCase().hashCode();
}

This code looks reasonable and will return the same hash code for upper-, mixed-, or lowercase strings. What's missing here is the trim.

Here are the results from running the previous JUnit test with the sabotaged method:

.F
Time: 0.01
There was 1 failure:
1) testHashCode(demo.LooselyComparableStringTest)
junit.framework.AssertionFailedError: expected:<32539678> 
   but was:<30602338>...

FAILURES!!!
Tests run: 1,  Failures: 1,  Errors: 0

I omitted some of the grizzly details, but it is obvious that something is wrong. In this case, the equal assertion test failed when testing to see that the hash code returned from each of the test strings is equal.

In addition to asserting a condition, you can fail a test at any point by calling the fail method. Here is an example of a test that calls fail explicitly when an exception is thrown:

public void testAddLocation() {

    try {
        LocationService locationService = new LocationService();

        for (int i = 0; i < 2; i++) {
            Location location = new Location();
            location.setAddressLine1("5751 Cameron Lane");
            location.setCity("Whitefish");
            location.setCountryCode("USA");

            locationService.add(location);
        }
    }
    catch (Throwable t) {
        t.printStackTrace(System.err);
        fail(t.getLocalizedMessage());
    }
}

This unit test fails whenever an error is thrown from the LocationService class. Failing a test on error is common. If I had not caught this error, JUnit would have failed but I would not had a chance to print a stack trace or show the cause of the failure. If you use fail, remember that control returns to JUnit as soon as you call fail.

Defining a Test

You create a test by extending the junit.framework.TestCase class. I like to be able to run my tests from a command line, so I include a main method that calls the junit.textui.TestRunner.run(Class) method and passes the test class as an argument. The run method then invokes any method that begins with test.

Your test code uses methods found in the TestCase super class to assert various conditions during the test run. You can compare values to be, for instance, equal, true, or not equal. If an assertion fails, it is noted and reported on.

Test methods are usually named after the methods they exercise, followed by the parameter types that are part of the method's signature. For example, the test for a method named myMethod that takes a string argument would be named testMyMethodString.

You can combine and run tests using a test suite. A test suite combines several tests and runs them in succession. The following code shows how you create a test suite:

package demo;

import junit.framework.Test;
import junit.framework.TestSuite;

public class AllTests {
    public static void main(String[] args) {
        junit.textui.TestRunner.run(AllTests.suite());
    }

    public static Test suite() {
        TestSuite suite = new TestSuite("Run all tests");
        suite.addTest(new TestSuite
		   (LooselyComparableStringMainTest.class));
        suite.addTest(new TestSuite(AS400DataSourceTest.class));
        return suite;
    }
}

The various test runners that come with JUnit run take a test, which is an interface or a class, as an argument. To run this suite with the textui test runner, you would call the main method using something like this:

java demo.AllTests

Installing and Running JUnit

You can install and run JUnit on any platform that supports Java. That means you can run your tests on a Windows PC, the iSeries, or a Linux server. The capability to run JUnit in so many environments is helpful when you develop code on your PC and deploy that code to another platform.

To install JUnit, go to the download page on the JUnit Web site. JUnit is distributed as a ZIP file containing a junit.jar file, documentation, and source. Just unzip the ZIP file and copy the junit.jar file onto any system that will run tests.

In addition to the textui test runner I showed you earlier, there are two graphical test runners. One is based on Swing and the other is based on the Abstract Window Toolkit (AWT). You use these in much the same way that you use the textui test runner, by calling either junit.swingui.TestRunner(Class) or junit.awtui.TestRunner(Class). Figure 1 shows the swing test runner's output.

Figure 1

Figure 1: The Swing test runner makes it easy to interpret test results

Once you have several tests to run, you might want to run them as a group. You have several options. The first is to create a test suite. The following example shows how you would set up and run a test suite:

TestSuite suite = new TestSuite();
suite.addTest(TestOne.suite());
suite.addTest(TestTwo.suite());
TestResult result = suite.run();

In addition to JUnit, you might want to install Ant, which is a scripting tool that can help you with script building, testing, and deploying Java applications. For more information on setting up and running Ant, see the Midrange Programmer article "Building Applications with Ant." Ant supports JUnit with the junit Ant task, which lets you perform regression testing as part of the build process.

A typical task to run JUnit in Ant looks like this:

<target  name="testall" depends="init,compile">
  <junit  showoutput="yes" haltonfailure="yes" fork="on">
    <formatter type="plain"/>
    <classpath refid="project.classpath.test">
    </classpath>
    <batchtest>
      <fileset dir="tests/" casesensitive="no">
        <include name="**/*Test.class"/>
      </fileset>
    </batchtest>
  </junit>    
</target>

The **/*Test.class include element creates a file set for testing that includes all class files ending in Test, found in the tests directory and subdirectories of the tests directory. Each of the selected tests runs; if there is a failure, the entire build halts because haltonfailure is set to yes. The JUnit Ant task can create a file describing the status of each test.

Overall, the JUnit Ant task is very configurable and is a good way to prevent distribution of Java classes containing errors. See the JUnit Task description for more information on the JUnit Ant task.

Using JUnit in Eclipse and WDSc

Many IDEs that support Java development provide tools to generate and run JUnit tests. Both WDSc (WebSphere Development Studio client) and Eclipse let you generate and run JUnit tests.

In either IDE, open the Java perspective and select and right-click a Java source file you would like to test. Next, select new, followed by other. On the next screen, click the plus sign (+) next to Java and select JUnit. Figure 2 shows what your screen should look like. Click TestCase and the next button.

Figure 2

Figure 2: In Eclipse or WDSc, use the new JUnit test dialog to create tests

Figure 3 shows what the next screen looks like. Change the test source folder, if you want to keep your tests separate from your application, and click next.

Figure 3

Figure 3: Just fill in the blanks and let Eclipse or WDSc create a JUnit test shell

On the next screen, choose any method you would like to test. I generally select all of the methods defined in the current class, because any test that you don't complete will pass. Click finish to generate a test stub.

Once you have generated a test stub, you need to add your tests. Go to the test method skeleton in your new test class and add code that exercises the Java class you are testing. Add calls to the assert or fail methods where appropriate.

Going Beyond Unit Testing

Unit testing will not guarantee that your applications will not fail. There are lots of other ways you should test before foisting applications onto your user community. You should at least test to make sure that applications meet user needs and that performance is adequate for the expected user load. You should also review your applications to make sure they are secure and follow standards.

As with unit testing, there are tools that can help you test these other areas. For load testing Web applications, there is Apache Software Foundation's JMeter, which lets you simulate user interaction with a Web site. With JMeter, you capture typical user interactions and run those interactions repeatedly while monitoring response times and overall application performance.

There are quite a few Java performance tools, but on the iSeries, the Performance Data Trace Visualizer is one of your best bets. You can download PDTV free from IBM's alphaWorks site. I have never used a performance analysis tool that gathered so much data so unobtrusively; just be warned that it gathers that data very quickly. Depending on your system, you'll likely be able to capture and analyze only a few interactions. On our system, I could reasonably work with about only 15 seconds' worth of trace data.

The value of load and performance testing cannot be overstated in a Java environment. It is too easy for a simple oversight or mistake to impact the overall scalability of an application. These problems crop up when you least expect them. Recently, we moved a new application into production and skipped load testing because of time constraints. Several hours later our Web site crashed, stopping the work of several hundred users. The problem was easy to fix once we realized that each user was building and never releasing a copy of a global XML configuration file, but what if it hadn't been a simple issue? What if our site had been down for many hours or days? The point is that a simple load test would have allowed us to catch the issue before promoting the software into production.

Other areas that are important to test or review are application integration, usability, and security. No tool can completely automate any of these areas, but automated unit testing can help.

Testing the Limits

There is more to testing than JUnit, but unit testing is an important part of any test strategy. I have come to rely more and more on unit tests. They have helped me to build better applications, with fewer errors. Those tests also contribute to the documentation of our applications. Overall, JUnit will save you time when testing applications and will help you to prevent promoting questionable code to production.

There are very few Java tools that are as mature as JUnit. Because JUnit has been around for so long, and is so widely recognized, there are lots of tools and products that work with it. Both Eclipse and WDSc provide built in wizards that let you build and run JUnit tests. The Ant build tool also supports JUnit. Combined, JUnit and Ant let you build applications and perform regression testing, allowing you to abort the build process when errors are detected.


David Morris is a software architect at Plum Creek Timber Company, and started the iSeries-toolkit open-source project. E-mail: dmorris@itjungle.com.


Sponsored By
T.L. ASHFORD

BARCODE400 by T.L. Ashford is the easiest
and fastest way to create and print Compliance
Labels directly from the AS/400 and iSeries.

Ashford's comprehensive library of Compliance formats is available to Barcode400 users. AIAG labels for Ford and Motorcraft, GM, and many more are available. BARCODE400 is backed by the best Technical Support Team in the industry.

FREE Guide to Bar Code Labeling

www.tlashford.com or call 800.541.4893



THIS ISSUE
SPONSORED BY:

T.L. Ashford
ASNA
Profound Logic Software
WorksRight Software


BACK ISSUES

TABLE OF
CONTENTS
JUnit Automates Java Testing

Back to Basics: Modifying a Subfile

Admin Alert: Hidden Secrets of the SBMJOB Command

OS/400 Alert: Filtering SMTP Server


Editors
Shannon O'Donnell
Kevin Vandever

Managing Editor
Shannon Pastore

Contributing Editors:
Howard Arner
Raymond Everhart
Joe Hertvik
Ted Holt
Marc Logemann
David Morris

Publisher and
Advertising Director:

Jenny Thomas

Advertising Sales Representative
Kim Reed

Contact the Editors
Do you have a gripe, inside dope or an opinion?
Email the editors:
editors@itjungle.com


Copyright © 1996-2008 Guild Companies, Inc. All Rights Reserved.