January 22, 2021
Hot Topics:

Test Cases Made Easy with JUnit 4.5

  • By David Thurmond
  • Send Email »
  • More Articles »

There are some very significant differences between the JUnit 4.5 test cases and the JUnit 3.8 version.

The first noteworthy difference is that the familiar junit.framework package is not used in NumberCruncherTestCase45.java. Furthermore, this class does not even extend the JUnit TestCase class. So, how can JUnit execute the test cases?

JUnit 4.5 uses a new feature available in JDK1.5 and later called assertions. The line:

import static org.junit.Assert.*;

is necessary, because the code must import the JUnit assertions rather than using the new Java assert keyword. Without this statement, the code will not compile.

Next, notice many instances of the @ sign followed by one or more keywords in the code that look like JavaDoc annotations. These annotations are the mechanism that JUnit 4.5 uses to work its magic.

The first example of this is the @Test annotation. In JUnit 3.8 and earlier, all test case methods must begin with "test"; for example, the testIsPrime() method. In JUnit 4.5, this convention is no longer necessary. Any method that has the @Test annotation before the method signature is recognized as a test case by the JUnit test runner and is executed as such. This flexibility means that you are free to name your test case methods as you see fit. Note, however, that it is necessary to import the org.junit package for the compiler to recognize the assertions and compile the code.

As mentioned earlier, it is no longer necessary to extend the junit.framework.TestCase class when writing a test case. This is particularly helpful, because Java does not allow multiple inheritance. Before, developers were locked in to creating objects of type TestCase, meaning that test cases had to be separated from the rest of the business logic within an application. Now, it is possible to write test cases directly inside of the business objects that are being tested. A business object can extend some other class and still be executable by the JUnit framework.

Another difference between the JUnit 4.5 and 3.8 architecture is the way in which test case fixtures are prepared and taken down. A fixture is a pre-existing condition that is necessary to run a test case, such as initialization of variables, allocation of database connections, and so on.

In JUnit 3.8, the setup() and teardown() methods were executed automatically before each "testXXX()" method was invoked. Now, it is only necessary to annotate the setup and destruction methods of test case fixtures with @Before and @After, respectively. The JUnit test runner will invoke the method annotated with @Before prior to running each @Test method, and will run the method annotated with @After when the test case is finished. As with the individual test case methods themselves, you are now free to name these test fixture methods as you see fit.

Similarly, it is now possible to create fixtures that run once at the beginning of the entire test case class's execution and once when all test cases are completed. Suppose the test cases required that an application's properties be initialized from a configuration file, or that a connection pool of JDBC connections should be created. Likewise, the connection pool would need to be deallocated when the test cases are done. Denoting the setup method with the @BeforeClass annotation and the cleanup method with the @AfterClass annotation will notify the JUnit test runner to invoke these methods when the class is initialized and after all tests are done.

In JUnit 3.8, the only way to handle this situation was to code class-level fixture setup in the constructor and class-level fixture tear-down in the object finalizer. JUnit 4.5 has improved upon this approach by introducing a true life cycle model for all phases of the testing process.

New Features of JUnit 4.5

So far, you have seen how JUnit 4.5 accomplishes the same things that could be accomplished in version 3.8 and earlier. Now, here are some juicy new tidbits of functionality to make writing test cases easier.

First, examine the testFactorialInvalid() method from NumberCruncherTestCase38.java below:

   public void testFactorialInvalid() {
      NumberCruncher nc = new NumberCruncher();
      boolean gotException = false;
      try {
         nc.factorial(new Integer(-4));
      } catch (IllegalArgumentException ex) {
         gotException = true;
      } //    catch
      assertTrue("Factorial Validation", gotException);
   }    // testFactorialInvalid()

Here, an invalid value of -4 is passed to the NumberCruncher class's factorial() method, and the anticipated IllegalArgumentException is caught in an exception-handling block. Although this is not overly complex, it still lends itself to the possibility of bugs in the test case itself.

Compare this to the same method from NumberCruncherTestCase45.java below:

@Test (expected = IllegalArgumentException.class)
   public void testFactorialInvalid() {
      NumberCruncher nc = new NumberCruncher();
      nc.factorial(new Integer(-4));
   }    // testFactorialInvalid()

Here, the expected= attribute of the @Test annotation tells JUnit to expect an IllegalArgumentException to be thrown when the factorial() method is invoked. This test case requires much less coding and is far less complex than its JUnit 3.8 counterpart above, greatly reducing the possibility of a bug occurring in the test case itself.

Best practices indicate that test cases should be written as a part of the development process, rather than being postponed until the code is long finished. In JUnit 3.8, doing this meant that a test case that was not yet ready for prime-time must either be commented out or renamed so that it didn't conform to the "testXXX()" convention for test case methods. To get around this, JUnit 4.5 has introduced the @Ignore annotation, as shown below:

   @Ignore ("Not ready yet")
   public void testSomethingElse() {
      NumberCruncher nc = new NumberCruncher();
   }    // testSomethingElse()

This annotation causes the JUnit test runner to disregard the test case completely until it is ready to be executed. The message is used for JUnit's output when it encounters an ignored test case.

Page 3 of 5

This article was originally published on November 12, 2008

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date