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:
- 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.
- Create a constructor for this class, passing a name that is
representative of the set of tests for this class as the parameter.
- 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.
- 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.
- 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.
- 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:
- Course.java
// this class is used for storing the information of a course
public class Course {
String course_name; // name of the course
int grade; // grade for this course
// Constructor
public Course(String nm, int gr) throws RuntimeException {
// check that the parameters are valid
if (nm == null || nm.equals("") || gr < 0 || gr > 100)
throw new RuntimeException("Illegal inputs for Course");
// store the course name and its grade
course_name = nm;
grade = gr;
}
// method to get the name of the course
public String getCourseName() { return course_name; }
// method to get the grade of the course
public int getCourseGrade() { return grade; }
}
- Student.java
import java.util.*;
// Student is a data manager that keeps a student's record
public class Student {
private String name; // name of the student
private String number; // the student's number
private Vector course_grades; // courses taken by student
// Constructor
public Student(String nm, String no) throws RuntimeException {
// check that the parameters are valid
if (nm == null || no == null)
throw new RuntimeException("Invalid inputs");
// store the student's name and number
name = nm;
number = no;
// initialize the vector that will store the student's grade
course_grades = new Vector();
}
// method to get the student's name
public String getStuName() { return name; }
// method to get the student's number
public String getStuNumber() { return number; }
// method to assign a grade to a course
public void assignGrade(String course, int score)
throws RuntimeException {
Course c = new Course(course, score);
course_grades.addElement(c);
}
// method to return the grade of a course
public int getGrade(String course) throws RuntimeException {
// check the inputs
if (course == null)
throw new RuntimeException("Invalid course name");
// find the course
for (Enumeration e = course_grades.elements();
e.hasMoreElements();) {
Course c = (Course)e.nextElement();
if (course.equals(c.getCourseName()))
return c.getCourseGrade();
} // for
// if course does not exists, throw exception
throw new RuntimeException("Course " + course + " does not exist");
}
}
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:
- pkb
- parser
- extractor
- preprocessor
- evaluator
- projector
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:
- 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
- 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;
- change the classpath to include this package (after setting the
classpath, you should recompile your source code again):
set CLASSPATH=%classpath%;C:\CS3214s
- 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:
- Create a subdirectory call test as follows:
mkdir C:\CS3214s\tutorial\test
- Place the StudentTest.java file in the subdirectory just created
- Ensure that StudentTest.java has the following (you will need
to recompile after editing the file):
package tutorial.test;
import tutorial.*;
- 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.
- The JUnit official web site
Everything you need to know about JUnit can be found here.
-
JUnit Primer
A quickstart guide - very useful to get you up and running fast.
-
Test Infected - Programmers Love Writing Tests
This document provides a more detailed look at using JUnit. If you have
a bit of time to spare, it's definitely worth reading.
-
JUnit A Cook's Tour and
A UML Testing Framework
Both these are useful in understanding the overall framework of JUnit.
They deal mainly with the design of JUnit. Useful if you want to
understand what goes behind the scenes in designing JUnit.
- JUnit best practices
One of the more recent documents on JUnit. This article describes
some useful ways for using JUnit as well as things you should
avoid when using it.