JUnit Introduction

Unit testing with JUnit

JUnit is a freely available, open source Unit Testing Framework. Unit testing is a good way to gain confidence is the correctness of an individual unit of your program (typically, one class). This tutorial will introduce you to using JUnit within the Eclipse environment.

Note that this year we are using JUnit 4, not JUnit 3.8. JUnit 4 has a different API to JUnit 3 -- make sure you are using the right version.

A Date Class

Let's suppose you have written the following Date class and you now want to test it. Your code looks like:
 public class MyDate {
  private int day;    // 1 <= day <= 31
  private int month;  // 1 <= month <= 12
  private int year;

  public MyDate(int _day, int _month, int _year) {
   day = _day;
   month=_month;
   year=_year;
   // check invariant holds
   if(day <= 0 || month < 0) {
    throw new RuntimeException("Cannot construct invalid Date!");
   } else if((month==4 || month==6 || month==9 || month==11) && day > 30) {
    throw new RuntimeException("Cannot construct invalid Date!");
   } else if(month == 2 && (day>29 || (day>28 && !(year%4==0 && (year%100 != 0 || year%400==0))))) {
    throw new RuntimeException("Cannot construct invalid Date!");
   } else if(day > 31 || month > 12) {
    throw new RuntimeException("Cannot construct invalid Date!");
   }
   // Date is valid!
  }

  public int day() { return day; }
  public int month() { return day; }
  public int year() { return day; }
 }

As you can see, it's pretty difficult to tell whether this code works correctly or not! So, we need to write some unit tests to give us some confidence.

Testing correct Dates

The first test we're going to write will check, for a number of valid dates, that the MyDate construct does not throw an exception. To do this, select "File->New->JUnit Test Case" from the menu in Eclipse.

junit-4-menu.gif

This will bring up the following window:

junit-4-dialog.gif

At this stage, we do need to do anything much complicated --- chose a JUnit 4 test case, and Click here if JUnit 4 is not on the build path! Then, click "Finish" and Eclipse will create an empty test case class for you. What we need to do now is to write a function to run the test:

 public class MyDateTest {
  @Test public void testConstructValidDate() {
   MyDate d;
   // all these dates are right!
   d = new MyDate(1,1,1);
   d = new MyDate(1,05,2006);
   d = new MyDate(28,02,2006);
   d = new MyDate(29,02,2008);
   d = new MyDate(31,05,2008);
  }
 }

As you can see, this simply creates a number of valid dates. If any of them result in an exception being thrown, the test will fail. To run the test, right-click on "MyDateTest.java" and select "Run As->Junit Test".

junit-4-runAs.gif

Eclipse will automatically show the Junit results pane, which looks like this:

junit-4-runOK.gif

The main thing is to see is the big green bar which indicates that all tests have passed! This captures the key advantage of using JUnit: we can easily run an entire suite of test cases and check which pass or fail.

Testing Exceptions in JUnit 4

JUnit 4 changed the way exceptions should be tested. By adding the expected parameter to the @Test annotation, we can insist that an exception is thrown. For a single test, we can now write just:
 @Test(expected= RuntimeError.class)
 public void testConstructInvalidDate1() {
    MyDate d = new MyDate(0, 0, 0)
 }
and JUnit 4 will worry about exceptions for us!

Test Fixtures

Sometimes you need to construct objects that you will reuse across several tests. For example, perhaps you want a couple of Dates you will compare. To do this, make a field for each object you want to keep (the test fixtures) and use a special @Before method to initialise them.
public class MyDateTest {
    private Date onejan1970;
    private Date sevenapril12008;

    @Before public void setUp() {
        onejan1970 = new Date(1,1,1970);
        sevenapril12008 = new Date(7,4,2008);
    }
}
All @Before methods will be run before each test (and any @After methods will be run afterwards.

Testing incorrect Dates

This section works, but is not good style in JUnit 4 --- see next para

To test for incorrect dates is slightly harder than for correct dates. The reason is that a single JUnit test will fail if an exception is thrown during the test. But, we want to check that our MyDate constructor correctly throws an exception for a number of invalid dates. Therefore, we need to catch the exceptions ourselves and direct JUnit to fail the test if an exception is not thrown. The test case looks like this:

 @Test public void testConstructInvalidDate() {
  int[][] tests={
   // all these test dates are wrong!
   {0,0,0},
   {1,0,0},
   {32,1,1},
   {29,2,2006},
   {31,9,2006},
   {31,4,2006},
   {31,6,2006},
   {31,6,2006}
  };

  for(int i=0;i!=tests.length;++i) {
   try {
    MyDate d = new MyDate(tests[i][0],tests[i][1],tests[i][2]);
   } catch(RuntimeException e) { continue; }
    fail();
   }
 }

This stores a list of the test cases in the variables tests. It then runs through each and attempts to construct a MyDate object. If an exception is thrown, then the test continues by moving onto the next. If an exception is not thrown, however, we indicate to JUnit that the test has failed by calling fail().

If you run this test with JUnit as before, you'll see that it does not pass:

junit-4-runFailure.gif

This time, the red bar indicates the test has failed. Looking in the "Failure Trace" we can see the failure is within testConstructInvalidDate(). We would need to do some additional testing to ascertain which case exactly failed, however. In fact, the problem is in the line if(day < 0 || month < 0)= which should, of course, check that month is less-than-or-equal to zero!

Parameterised Tests in JUnit 4

What's more, JUnit 4 also supports test parameters. If we write
@RunWith(Parameterized.class)
public class MyDateTest2 {
  @Parameters
  public static Collection data() {
    return Arrays.asList(new Object[][] {
   {0,0,0},   {1,0,0},   {32,1,1},   {29,2,2006},   {31,9,2006},
   {31,4,2006},   {31,6,2006},   {31,6,2006} });}

  private int d;
  private int m;
  private int y;

  public MyDateTest2(int dd, int mm, int yy) {d=dd; m=mm; y=yy;}

  @Test(expected=RuntimeException.class)
  public void invalideDateTest() {
           new MyDate(d, m, y);
  }
}
to we get the same effect as above, but with much less effort. @RunWith(Parameterized.class) says treat this as a special parameterised class that has a @Parameters data method containing the test parameters. The test class is instantiated once for each test, and for each set of parameters, using the constructor with the same number of parameters as each entry in the data collection.

Then of course @Test(expected=RuntimeException.class) takes care of all the exception stuff.

The only slightly tricky point is that you need to import loads of stuff --- but eclipse will do this for you:
import static org.junit.Assert.*;
import org.junit.Test;

import org.junit.runner.*;
import org.junit.runners.*;
import org.junit.runners.Parameterized.Parameters;


import java.util.Arrays;
import java.util.Collection;

Further Reading

Some useful resources on JUnit are:
  • www.junit.org - The best place to find information on JUnit
  • The JUnit cookbook
  • Google will also find some web tutorials, but many of them are for JUnit 3, not JUnit 4.