JUnit
by Matthew Phillips
What is JUnit?
JUnit is a unit-testing framework. Unit-testing is a means of verifying the results you expect from your classes. Why perform unit testing? It leads to better code. If you write your test before hand and focus your code on passing the test you are more likely to end up with simple code that does what it should and nothing more. Unit-testing also assists in the refactoring process. If your code passes the unit-test after refactoring you will know that you haven't introduced any bugs to it.
Installing JUnit
JUnit makes unit-testing very easy to do. First you need to download JUnit from JUnit.org. Unzip the file and add the included junit.jar to your classpath. JUnit includes three TestRunners based on your user interface needs: a text based user interface, an AWT based user interface, and a Swing based user interface. I will briefly introduce each and then use the swing interface for the rest of the tutorial.
A simple implementation
The following is the code for our simple test case:
import junit.framework.* ;
public class LineItemTest extends TestCase {
protected LineItem item ;
public void setUp() {
item = new LineItem( "Book" , 50.00 );
System.out.println( "Line item setup" );
}
public void tearDown() {
item = null ;
System.out.println( "Line item teardown" );
}
public void testGetMethods() {
System.out.println( "test get methods" );
assertEquals( "Book" , item.getProduct() );
assertTrue( 50.00 == item.getPrice() );
}
public void testBlank() {
System.out.println( "this is a blank test" );
}
}
Unit-tests are implemented as methods contained within one or more test classes. All test classes extend the TestCase class in the junit.framework package. I have included the System.out.println calls to show the general flow that the TestRunner takes. The setUp() and tearDown() methods are used to run code that is common to all test methods. They are called for each test method invoked by the test runner. The setUp() method will be called by the test runner before each test method. The tearDown() will be called after each test method. You may call your test methods anything you like, but if you start their name with test you will not have to override any other methods for the TestRunner to find them. The testGetMethods() contains two method calls. The assertEquals() method has two parameters: our expected results of the getProduct() method and a call to the getProduct method of LineItem. It will then test their equality based on the equals method in the class passed to it. The assertTrue() method checks the boolean passed to it. If either of these methods fail they will throw an AssertionFailedError which the TestRunner will catch and display. I included a testBlank() method so that the TestRunner will operate on two tests. One thing to keep in mind for versions of JUnit prior to 3.8 is that you will also be required to add a construtor that takes a String parameter and calls the parent classes constructor with that parameter. A public, no parameter constructor was added to the TestCase class in 3.8 thus eliminating the need to implement a constructor. The code for the LineItem class:
public class LineItem {
private String product ;
private double price ;
public LineItem( String product , double price ) {
this.product = product ;
this.price = price ;
}
public String getProduct() {
return product ;
}
public double getPrice() {
return price ;
}
}
Compile both of these. To run the text based test runner type the following command at a command prompt:
java junit.textui.TestRunner LineItemTest
You should receive the results of the println calls and an OK message with the time the test took to run. JUnit doesn't bother with a lot of messages telling you a test passed. It only delivers the messages when the tests fail. The command for the AWT user interface is java junit.awtui.TestRunner. The Swing based interface command is java junit.swinui.TestRunner.
Implementing your own test
Writing your own test case is simple. We will extend on our LineItem example by implementing a sales order. Our test case will test the sales order's ability to return a total. The test case will look like this:
import junit.framework.* ;
public class SalesOrderTest extends TestCase {
protected LineItem item1 ;
protected LineItem item2 ;
protected SalesOrder order ;
public void setUp() {
item1 = new LineItem( "Book" , 50.00 );
item2 = new LineItem( "Tape" , 10.00 );
order = new SalesOrder();
order.add( item1 );
order.add( item2 );
}
public void tearDown() {
item1 = null ;
item2 = null ;
order = null ;
}
public void testTotal() {
double total = item1.getPrice() + item2.getPrice();
assertEquals( total , order.getTotal() , 0.0 );
LineItem item3 = new LineItem( "Video" , 39.95 );
order.add( item3 );
total = total + item3.getPrice();
assertEquals( total , order.getTotal() , 0.0 );
assertTrue( order.getTotal() == total );
}
}
The assertEquals() method used here takes a third parameter. The floating point variants of the assertEquals() method use this third parameter to test your tolerance for floating point accuracy. In this case if the numbers are not an exact match it will throw an AssertionFailedError. Writing up a test case has brought a couple of interface necessities to the forefront. The SalesOrder class will need a no-parameter constructor, and add method that takes a line item as a parameter and a getTotal method that will return a total of some type. The first implementation of SalesOrder will look like this:
public class SalesOrder {
public SalesOrder() {
}
public void add( LineItem item ) {
}
public double getTotal() {
return 0.0 ;
}
}
Both of these will compile at this stage. To run the test case type the following on the command line:
java junit.swingui.TestRunner SalesOrderTest
You should see the following error:
junit.framework.AssertionFailedError: expected:<60.0>but was:<0.0>
In this case the assertTrue() never ran. JUnit stops a test method on the first error it finds. If your test class has other methods, the TestRunner will run those other methods even if a previous method failed. I'll leave it to you to implement the code to make the test method in SalesOrderTest pass.
Running multiple test cases
The demonstration so far has run the test cases separately. For two classes that is not that big a problem, but hundreds of classes would be cumbersome. Fortunately, the developers of JUnit have anticipated this need by adding the TestSuite class. Here is some sample code for the previous tests:
import junit.framework.* ;
public class AllTests {
public static TestSuite suite() {
TestSuite suite = new TestSuite();
suite.addTestSuite( LineItemTest.class );
suite.addTestSuite( SalesOrderTest.class );
return suite ;
}
}
Instead of running either of the previous classes with the test runner you will run the AllTests class. The TestRunner will call the suite method and receive the TestSuite object that contains the other tests. It will then be able to run each test.
That's it
JUnit really is just that simple. There are other ways to implement your tests using JUnit, but I found this method to be the easiest for me. There are many other articles and tutorials on the JUnit website to learn more about using this powerful tool.
Return to Top
|