JUnit best practices

Techniques for building resilient, relocatable, multithreaded JUnit tests

Page 3 of 3
java -cp C:\project\classes;C:\junit3.2\junit.jar:C:\jcf\jcfutils.zip -Dclass_root=C:\project\classes -Dtest_type=UNIT junit.ui.TestRunner bp.TestAll

This command loads and runs all test cases of type UNIT that have classes stored under C:\project\classes.

Test thread safety

You'll want to guarantee the status of supposedly thread-safe classes by testing them. Such tests prove difficult using Junit 3.2's existing set of facilities. You can use junit.extensions.ActiveTest to run a test case in a different thread. However, TestSuite assumes that a test case is complete when it returns from run(); with junit.extensions.ActiveTest, it is not. We could work hard to define a properly working ActiveTestSuite; instead, let's look at a simpler solution: MultiThreadedTestCase. First, I'll show how MultiThreadedTestCase assists with multithreaded testing. Then I'll show how MultiThreadedTestCase is implemented.

To use MultiThreadedTestCase, we implement the standard elements of a TestCase, but we derive from MultiThreadedTestCase. The standard elements are the class declaration, the constructor, and since we're using TestAll, the definition of the test type:

public class MTTest extends MultiThreadedTestCase {
   /**
    * Basic constructor - called by the test runners.
    */
   public MTTest(String s) {
      super (s);
   }
   public static final String TEST_ALL_TEST_TYPE = "UNIT";

A multithreaded test case needs to spawn a number of threads that perform some operation. We need to start those threads, wait until they've executed, and then return the results to JUnit -- all done in the code below. The code is trivial; in practice, this code would spawn multiple threads that performed different operations on the class under test. After each operation the class invariants and post-conditions would be tested to ensure that the class was behaving properly.

   public void testMTExample ()
   {
      // Create 100 threads containing the test case.
      TestCaseRunnable tct [] = new TestCaseRunnable [100];
      for (int i = 0; i < tct.length; i++)
      {
         tct[i] = new TestCaseRunnable () {
            public void runTestCase () {
               assert (true);
            }
         };
      }
      // Run the 100 threads, wait for them to complete and return the results to JUnit.
      runTestCaseRunnables (tct);
   }
}

Now that I've shown how to use MultiThreadedTestCase, I'll examine the implementation. First, we declare the class and add an array where the running threads will be stored:

public class MultiThreadedTestCase extends TestCase {
   /**
    * The threads that are executing.
    */
   private Thread threads[] = null;

testResult, seen below, holds the testResult that declares that the test case's run() will be passed. We override run() so we can store the testResult for later population by the test threads:

   /**
    * The tests TestResult.
    */
   private TestResult testResult = null;
   /**
    * Simple constructor.
    */
   public MultiThreadedTestCase(final String s) {
      super(s);
   }
   /**
    * Override run so we can save the test result.
    */
   public void run(final TestResult result) {
      testResult = result;
      super.run(result);
      testResult = null;

runTestCaseRunnables() runs each TestCaseRunnable in a seperate thread. All the threads are created and then started at the same time. The method waits until every thread has finished and then returns:

   protected void runTestCaseRunnables (final TestCaseRunnable[] runnables) {
      if(runnables == null) {
         throw new IllegalArgumentException("runnables is null");
      }
      threads = new Thread[runnables.length];
      for(int i = 0;i < threads.length;i++) {
         threads[i] = new Thread(runnables[i]);
      }
      for(int i = 0;i < threads.length;i++) {
         threads[i].start();
      }
      try {
         for(int i = 0;i < threads.length;i++) {
            threads[i].join();
         }
      }
      catch(InterruptedException ignore) {
         System.out.println("Thread join interrupted.");
      }
      threads = null;
   }

Exceptions caught in the test threads must be propagated into the testResult instance we saved from the run() method. handleException(), below, does just that:

   /**
    * Handle an exception. Since multiple threads won't have their
    * exceptions caught the threads must manually catch them and call
    * handleException().
    * @param t Exception to handle.*/
   private void handleException(final Throwable t) {
      synchronized(testResult) {
         if(t instanceof AssertionFailedError) {
            testResult.addFailure(this, (AssertionFailedError)t);
         }
         else {
            testResult.addError(this, t);
         }
      }
   }

Finally, we define the class that each test thread extends. The purpose of this class is to provide an environment (runTestCase()) where thrown exceptions will be caught and passed to JUnit. The implementation of this class is:

   /**
    * A test case thread. Override runTestCase () and define
    * behaviour of test in there.*/
   protected abstract class TestCaseRunnable implements Runnable {
      /**
       * Override this to define the test*/
      public abstract void runTestCase()
                 throws Throwable;
      /**
       * Run the test in an environment where
       * we can handle the exceptions generated by the test method.*/
      public void run() {
         try {
            runTestCase();
         }
         catch(Throwable t) /* Any other exception we handle and then we interrupt the other threads.*/ {
            handleException(t);
            interruptThreads();
         }
      }
   }
}

The implementation above helps to develop multithreaded test cases. It handles exceptions thrown in the multiple testing threads and passes them back to JUnit. JUnit only sees a test case that behaves like a single-threaded test. The unit test developer can extend that test case to develop multithreaded tests, without spending much time developing thread-handling code.

Conclusion

Using JUnit to develop robust tests takes some practice (as does writing tests). This article contains a number of techniques for improving your tests' usefulness. Those techniques range from avoiding basic mistakes (such as not using setUp()) to more design-level issues (avoiding intertest coupling). I've covered some basic ideas to help you use JUnit to test parts of your UI or Web application. I've also shown how to build an automated test suite that removes the overhead of maintaining hand-coded test suites and a mechanism for reducing the effort of developing multithreaded JUnit test cases.

JUnit is an excellent framework for unit-testing Java applications. One final thought: If you just started using JUnit to produce unit tests, stick at it. For the first few weeks, you may not see any real reward for your labors. In fact, you may feel that the whole process slows you down. However, after a few weeks, you'll begin to enhance existing code. Then you'll run your tests, pick up new bugs, and fix them. You'll be far more confident in your code base and you will see the value of unit testing.

Learn more about this topic

| 1 2 3 Page 3
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.