By Barry Burd

JUnit is a standardized framework for testing Java units (that is, Java classes). JUnit can be automated to take the some of the work out of testing.

Imagine you’ve created an enum type with three values: GREEN, YELLOW, and RED. Listing 1 contains the code:

Listing 1

public enum SignalColor {
  GREEN, YELLOW, RED
}

A traffic light has a state (which is a fancy name for the traffic light’s color).

public class TrafficLight {
  SignalColor state = SignalColor.RED;

If you know the traffic light’s current state, you can decide what the traffic light’s next state will be.

public void nextState() {
  switch (state) {
  case RED:
    state = SignalColor.GREEN;
    break;
  case YELLOW:
    state = SignalColor.RED;
    break;
  case GREEN:
    state = SignalColor.YELLOW;
    break;
  default:
    state = SignalColor.RED;
    break;
  }
}

You can also change the traffic light’s state a certain number of times:

public void change(int numberOfTimes) {
  for (int i = 0; i < numberOfTimes; i++) {
    nextState();
  }
}

Putting it all together, you have the TrafficLight class in Listing 2.

Listing 2

public class TrafficLight {
  SignalColor state = SignalColor.RED;
  public void nextState() {
    switch (state) {
    case RED:
      state = SignalColor.GREEN;
      break;
    case YELLOW:
      state = SignalColor.RED;
      break;
    case GREEN:
      state = SignalColor.YELLOW;
      break;
    default:
      state = SignalColor.RED;
      break;
    }
  }
  public void change(int numberOfTimes) {
    for (int i = 0; i < numberOfTimes; i++) {
      nextState();
    }
  }
}

In the olden days you might have continued writing code, creating more classes, calling the nextState and change methods in Listing 2. Then, after several months of coding, you’d pause to test your work.

And what a surprise! Your tests would fail miserably! You should never delay testing for more than a day or two. Test early and test often!

One philosophy about testing says you should test each chunk of code as soon as you’ve written it. Of course, the phrase “chunk of code” doesn’t sound very scientific. It wouldn’t do to have developers walking around talking about the “chunk-of-code testing” that they did this afternoon. It’s better to call each chunk of code a unit, and get developers to talk about unit testing.

The most common unit for testing is a class. So a typical Java developer tests each class as soon as the class’s code is written.

So how do you go about testing a class? A novice might test the TrafficLight class by writing an additional class — a class containing a main method. The main method creates an instance of TrafficLight, calls the nextState and change methods, and displays the results. The novice examines the results and compares them with some expected values.

After writing main methods for dozens, hundreds, or even thousands classes, the novice (now a full-fledged developer) becomes tired of the testing routine and looks for ways to automate the testing procedure. Tired or not, one developer might try to decipher another developer’s hand-coded tests. Without having any standards or guidelines for constructing tests, reading and understanding another developer’s tests can be difficult and tedious.

So JUnit comes to the rescue!.

To find out how Eclipse automates the use of JUnit, do the following:

  1. Create an Eclipse project containing Listings 1 and 2.

    The package explorer with an open project.

  2. In Windows, right-click the Package Explorer’s TrafficLight.java branch. On a Mac, control-click the Package Explorer’s TrafficLight.java branch.

    A context menu appears.

  3. In the context menu, select New→JUnit Test Case.

    As a result, the New JUnit Test Case dialog box appears.

    the New JUnit Test Case dialog box

  4. Click Next at the bottom of the New JUnit Test Case dialog box.

    As a result, you see the second page of the New JUnit Test Case dialog box. The second page lists methods belonging (either directly or indirectly) to the TrafficLight class.

    the second page of the New JUnit Test Case dialog box

  5. Place a checkmark in the checkbox labeled Traffic Light.

    As a result, Eclipse places checkmarks in the nextState() and change(int) checkboxes.

  6. Click Finish at the bottom of the New JUnit Test Case dialog box.

    JUnit isn’t formally part of Java. Instead comes with its own set of classes and methods to help you create tests for your code. After you click Finish, Eclipse asks you if you want to include the JUnit classes and methods as part of your project.

    Eclipse asks you if you want to include the JUnit classes and methods as part of your JUnit test case.

  7. Select Perform the Following Action and Add JUnit 4 Library to the Build Path. Then click OK.

    Eclipse closes the dialog boxes and your project has a brand new TrafficLightTest.java file. The file’s code is shown in Listing 3.

    The code in Listing 3 contains two tests, and both tests contain calls to an unpleasant sounding fail method. Eclipse wants you to add code to make these tests pass.

  8. Remove the calls to the fail method. In place of the fail method calls, type the code shown in bold in Listing 4.

  9. In the Package Explorer, right-click (in Windows) or control-click (on a Mac) the TrafficLightTest.java branch. In the resulting context menu, select Run As→JUnit Test.

    Eclipse might have more than one kind of JUnit testing framework up its sleeve. If so, you might see a dialog box like the one below. If you do, select the Eclipse JUnit Launcher, and then click OK.

    The Select the preferred launcher dialog box in Eclipse.

    As a result, Eclipse runs your TrafficLightTest.java program. Eclipse displays the result of the run in front of its own Package Explorer. The result shows no errors and no failures. Whew!

    The results of a JUnit test

Listing 3

import static org.junit.Assert.*;
import org.junit.Test;
public class TrafficLightTest {
  @Test
  public void testNextState() {
    fail("Not yet implemented");
  }
  @Test
  public void testChange() {
    fail("Not yet implemented");
  }
}

Listing 4

import static org.junit.Assert.*;
import org.junit.Test;
public class TrafficLightTest {
  @Test
  public void testNextState() {
    TrafficLight light = new TrafficLight();
    assertEquals(SignalColor.RED, light.state);
    light.nextState();
    assertEquals(SignalColor.GREEN, light.state);
    light.nextState();
    assertEquals(SignalColor.YELLOW, light.state);
    light.nextState();
    assertEquals(SignalColor.RED, light.state);
  }
  @Test
  public void testChange() {
    TrafficLight light = new TrafficLight();
    light.change(5);  
    assertEquals(SignalColor.YELLOW, light.state);
  }
}

When you select Run As→JUnit Test, Eclipse doesn’t look for a main method. Instead, Eclipse looks for methods starting with @Test and other such things. Eclipse executes each of the @Test methods.

Things like @Test are Java annotations.

Listing 4 contains two @Test methods: testNextState and testChange. The testNextState method puts the TrafficLight nextState method to the test. Similarly, the testChange method flexes the TrafficLight change method’s muscles.

Consider the code in the testNextState method. The testNextState method repeatedly calls the TrafficLight class’s nextState method and JUnit’s assertEquals method. The assertEquals method takes two parameters: an expected value and an actual value.

  • In the topmost assertEquals call, the expected value is SignalColor.RED. You expect the traffic light to be RED because, in Listing 2, you initialize the light’s state with the value SignalColor.RED.

  • In the topmost assertEquals call, the actual value is light.state (the color that’s actually stored in the traffic light’s state variable).

If the actual value equals the expected value, the call to assertEquals passes and JUnit continues executing the statements in the testNextState method.

But if the actual value is different from the expected value, the assertEquals fails and the result of the run displays the failure. For example, consider what happens when you change the expected value in the first assertEquals call in Listing 4:

@Test
public void testNextState() {
  TrafficLight light = new TrafficLight();
  assertEquals(SignalColor.YELLOW, light.state);

Immediately after its construction, a traffic light’s color is RED, not YELLOW. So the testNextState method contains a false assertion and the result of doing Run As→JUnit looks like a child’s bad report card.

Results of a JUnit test when there is an error in the code.

Having testNextState before testChange in Listing 4 does not guarantee that JUnit will execute testNextState before executing testChange. If you have three @Test methods, JUnit might execute them from topmost to bottommost, from bottommost to topmost, from the middle method to the topmost to the bottommost, or in any order at all. JUnit might even pause in the middle of one test to execute parts of another test. That’s why you should never make assumptions about the outcome of one test when you write another test.

You might want certain statements to be executed before any of the tests begin. If you do, put those statements in a method named setUp, and preface that method with a @Before annotation. (See the setUp() checkbox in the figure at Step 3 in Listing 2, above.)

Here’s news: Not all assertEquals methods are created equal! Imagine adding a Driver class to your project’s code. “Driver class” doesn’t mean a printer driver or a pile driver. It means a person driving a car — a car that’s approaching your traffic light. For the details, see Listing 5.

Listing 5

public class Driver {
  public double velocity(TrafficLight light) {
    switch (light.state) {
    case RED:
      return 0.0;
    case YELLOW:
      return 10.0;
    case GREEN:
      return 30.0;
    default:
      return 0.0;
    }
  }
}

When the light is red, the driver’s velocity is 0.0. When the light is yellow, the car is slowing to a safe 10.0. When the light is green, the car cruises at a velocity of 30.0.

(In this example, the units of velocity don’t matter. They could be miles per hour, kilometers per hour, or whatever. The only way it matters is if the car is in Boston or New York City. In that case, the velocity for YELLOW should be much higher than the velocity for GREEN, and the velocity for RED shouldn’t be 0.0.)

To create JUnit tests for the Driver class, follow Steps 1 to 9 listed previously in this article, but be sure to make the following changes:

  • In Step 2, right-click or control-click the Driver.java branch instead of the TrafficLight.java branch.

  • In Step 5, put a check mark in the Driver branch.

  • In Step 8, remove the fail method calls to create the DriverTest class shown in Listing 6. (The code that you type is shown in bold.)

Listing 6

import static org.junit.Assert.*;
import org.junit.Test;
public class DriverTest {
  @Test
  public void testVelocity() {
    TrafficLight light = new TrafficLight();
    light.change(7);
    Driver driver = new Driver();
    assertEquals(30.0, driver.velocity(light), 0.1);  
  }
}

If all goes well, the JUnit test passes with flying colors. (To be more precise, the JUnit passes with the color green!) So the running of DriverTest isn’t new or exciting. What’s exciting is the call to assertEquals in Listing 6.

When you compare two double values in a Java program, you have no right to expect on-the-nose equality. That is, one of the double values might be 30.000000000 while the other double value is closer to 30.000000001. A computer has only 64 bits to store each double value, and inaccuracies creep in here and there. So in JUnit, the assertEquals method for comparing double values has a third parameter. The third parameter represents wiggle room.

In Listing 6, the statement

assertEquals(30.0, driver.velocity(light), 0.1);

says the following: “Assert that the actual value of driver.velocity(light) is within 0.1 of the expected value 30.0. If so, the assertion passes. If not, the assertion fails.”

When you call assertEquals for double values, selecting a good margin of error can be tricky. These figures illustrate the kinds of things that can go wrong.

error that occurs when the margin of the assertequals variable is too narrow.

Here, your margin of error is too small.

Error that appears when the margin of an AssertEquals variable is too large.

There, your margin of error is too large.

Fortunately, in this DriverTest example, the margin 0.1 is a very safe bet. Here’s why:

  • When the assertEquals test fails, it fails by much more than 0.1.

    Failure means having a driver.velocity(light) value such as 0.0 or 10.0.

    A correctly implemented margin of error for the AssertEquals variable.

  • In this example, when the assertEquals test passes, it probably represents complete, on-the-nose equality.

    The value of driver.velocity(light) comes directly from the return 30.0 code in Listing 5. No arithmetic is involved. So the value of driver.velocity(light) and the expected 30.0 value should be exactly the same (or almost exactly the same).