Crash Course in Using JUnit

Introduction

This document will introduce you to a testing framework called JUnit. JUnit is developed by Erich Gamma and Kent Beck. Its main purpose is to support developers in doing their unit testing of Java programs. JUnit can be used with JDK 1.1.*, JDK 1.2.* and JDK 1.3.*. In your project, we expect you to use JUnit extensively when testing your programs. Recall that one of the required program quality attributes for your project is reliability. JUnit can be used to help you achieve that.

Please note that in this document, I have included only the bare minimum that is sufficient to get you started. To use JUnit effectively, you will need to read more documentation. The references at the end of this document will point you to other useful information. In the next section, I will give you an overview of how to use JUnit followed by a detailed example. Then, I will provide suggestions on how you can package your programs before ending with a list of useful references.


Using JUnit

Overview

In this section, I will provide you with some general steps on how to go about using JUnit. The next section illustrates the key steps using an example. My advice for you is to skim through this section quickly for a quick overview. Then, as you go through the example in the next section, refer back to this section frequently to get the whole picture.

Assuming that you want to test a class called Parser. The following are the general steps to use the JUnit framework to test this class:

  1. Write a class (let's call it TestParser) to test the Parser class. This class must extend the class TestCase which is defined by the JUnit framework.
  2. Create a constructor for this class, passing a name that is representative of the set of tests for this class as the parameter.
  3. Create a fixture. A test fixture is a set of sample objects that you want to (re)use during testing. For example, you might create a few sample source files for the Parser to parse. JUnit provides a setUp and a tearDown method to manage the fixture. Therefore, you can eg. create file objects in setUp to open the source files and release these resources in the tearDown method. The important thing to note is that setUp and tearDown will be called for every 'test' that you run.
  4. Each 'test' you perform is represented by the implementation of a method in the test class. For example, if you want to test whether the parser extracts the tokens correctly, you can implement a method called testGetToken. An important point is that each method name begins with the word 'test'. This is necessary because JUnit uses reflection to know which tests to run. The collection of test methods you implement forms a test suite.
  5. In each test method you create, use the assertion mechanism provided by JUnit to compare the results of running the tests and the results you expected. This will enable you to create repeatable tests as well as saving you lots of time from visually inspecting the results.
  6. Finally, run the tests. There are two ways of running the tests. JUnit provides a TestRunner tool that can be invoked from the command line to run the tests and display the results. (there are both text and graphical versions). Alternatively, you can create a main method which invokes the TestRunner for execution. Internally, JUnit creates a test suite object that contains all the test methods of the testing class and execute each method. As each test is run, JUnit will provide feedback on whether the test run successfully, or the test failed, or an exception has occurred.

Example

In this section, I will describe how you can use JUnit using an example. Take a few minutes to examine the following two Java files to see what the two classes are doing: Basically, there are two files: Course.java contains the class definition of a course. Each Course object contains a name eg. CS3214s and an integer grade which ranges from 0 to 100. Student.java contains the class definition of a Student. Each student has a name, a number as well as a list of course grades. You can add the grade that a student scores at a particular course using the assignGrade method and retrieve the grade of a particular course using the getGrade method. The following is the test file I wrote for the Student class (called StudentTest.java):
import junit.framework.*;    // Note 1

public class StudentTest extends TestCase {    // Note 2
  // constructor - Note 3
  public StudentTest(String name) {
    super(name);
  }
  
  // method to test the constructor of the Student class
  public void testConstructor() {    // Note 4
    String student_name = "Jimmy";
    String student_no = "946302B";
    
    // create a new student
    Student stu = new Student(student_name, student_no);
    
    // verify that the object is constructed properly - Note 5
    assertEquals("student name wrong", student_name, stu.getStuName());
    assertTrue("student no. wrong", stu.getStuNumber().equals(student_no));
    
    // create some illegal inputs - Note 6
    try { 
      Student s = new Student("Jimmy", null); 
      fail("Constructor allows null student number"); 
    } catch (RuntimeException e) {}

    try { 
      Student s = new Student(null, "980921C"); 
      fail("Constructor allows null student name"); 
    } catch (RuntimeException e) {}
  }
    
  // method to test the assigning and retrieval of grades
  public void testAssignAndRetrieveGrades() {
    // create a student
    Student stu = new Student("Jimmy", "946302B");
    
    // assign a few grades to this student
    stu.assignGrade("cs2102", 60);
    stu.assignGrade("cs2103", 70);
    stu.assignGrade("cs3214s", 80);
    
    // verify that the assignment is correct
    assertTrue("fail to assign cs2102 grade", stu.getGrade("cs2102") == 60);
    assertTrue("fail to assign cs2103 grade", stu.getGrade("cs2103") == 70);
    
    // attempt to retrieve a course that does not exist
    try { 
      stu.getGrade("cs21002");
      fail("fail to catch non-existent course name");
    } catch (RuntimeException e) { }
  }
  
  // method create a test suite - Note 7
  public static Test suite() {
    return new TestSuite(StudentTest.class);
  }
  
  // the main method - Note 8
  public static void main(String args[]) {
    junit.textui.TestRunner.run(suite());
  }
}
Notes for the preceding code: To run the test suite, simply type:
java StudentTest
and you're off doing your unit testing :-)

Exercise

The best way to learn JUnit is to use it. So, here's a small exercise you can do to get some hands-on practice. Let's say we now extend the Student class by adding a method to find the average grade of all the courses taken by the student. You can add the following piece of code to Student.java:
// method to find the average grade of all the courses taken by the student
public float findAveGrade() {
    // if the student has not taken any courses, return 0 marks
    if (course_grades.isEmpty()) return 0.0f;

    // otherwise, find the average grade
    int total = 0;
    for (Enumeration e = course_grades.elements(); e.hasMoreElements();) {
        Course c = (Course)e.nextElement();
        total += c.getCourseGrade();
    }

    return (float)total / course_grades.size();
}
Your job is to write a method in StudentTest.java to test this newly created method. Give it a try and see whether you really know how to use JUnit ;-)

Packaging your source code

Organizing the project code into packages

As you go through each iteration of your project, you will find the amount of source code increasing. If you do not organize properly, you will soon end up with source files lying all over the directory, making it difficult to find the right file(s) or coordinate codes written by different members. In Java, you can organize your source code into packages. Each package contains a set of classes that serve a particular purpose. For example, here's a suggested way of organizing your codes for the project into packages: For those not familiar with implementing packages in Java, the following illustrates how to create a package using the sample programs I shown you earlier:
  1. Let's assume that we want to put the Student and Course classes in a package called tutorial. The first thing is to create a subdirectory called tutorial as follows (assuming you're using the Windows OS):
    mkdir C:\CS3214s\tutorial
  2. place Student.java and Course.java in the subdirectory that you just created. Add the following as the first line in each source file (i.e. Course.java and Student.java) that resides in the tutorial subdirectory:
    package tutorial;
  3. change the classpath to include this package (after setting the classpath, you should recompile your source code again):
    set CLASSPATH=%classpath%;C:\CS3214s
  4. for classes that need to use those classes in the tutorial package, add the following line in those classes:
    import tutorial.*;
Although it's not a must, we strongly encourage you to organize your source code into packages for ease of maintenance. After all, it's your team that will benefit from it, not us.

Organizing the test code

Following the guideline of "Code a little, test a little, code a little, test a little,...", you'll soon find the amount of test code increasing rapidly as the project progresses. A recommended way to organize your test code is to put all the test code for classes of a particular package as a sub-package. Let's say I now want to organize the tests for my tutorial package:
  1. Create a subdirectory call test as follows:
    mkdir C:\CS3214s\tutorial\test
  2. Place the StudentTest.java file in the subdirectory just created
  3. Ensure that StudentTest.java has the following (you will need to recompile after editing the file):
    package tutorial.test;
    import tutorial.*;
    	
  4. To run the test, type the following:
    java tutorial.test.StudentTest
In this way, you will find it easier to locate your test code and do regression testing.

References

I need to remind you that what I have just covered is the minimum that you need to know to get started using JUnit. There are many more useful features such as using fixtures, setUp and tearDown methods, ExceptionTestCase class, gui interface etc. You can find out about them from the references below (most can be found in the docs directory) and don't forget the javadocs that come with the JUnit package you download.