Testing in an Extreme Programming Setting

Joseph Bergin
Pace University
jbergin@pace.edu

 

Updated for JUnit 3.7.

If you are studying object-oriented software engineering, then you ought to explore Extreme Programming and the Scrum Team Organization.

In particular, you should explore Extreme Programming at the Wiki Web site. One of the features of XP (Extreme Programming) is a sophisticated testing system that you can read about on the Wiki XP pages. One place that explains the testing system is a paper by Kent Beck and Erich Gamma. You can also download a testing framework to use in guaranteeing the quality of your own code. I'm going to show how to use this frame work here.

The XP site at Wiki has a testing framework, JUnit, that Software Engineering teams can use to maintain a test scaffolding for a project. I've built three test classes to show how it is done and have successfully run them both on the Macintosh with Codewarrior and on the PC with Kawa and also with Codewarrior.

The advantage of this Extreme Testing framework, is that it is completely independent of the code to be tested. All of the test code is separate from all of the application code. Thus, you don't run the risk of introducing a bug just because you add a test.

The framework can be used both for unit test (at the class level) and overall system integration test. I will give examples of both uses using the order processing project. Code for this project can be found at the bottom of this page. You should find the extreme test material on Wiki and download the latest JUnit test framework. It is only about 150K zipped. You should unzip it into a new directory that you create. This can be within your project working directory if you like. Find the file named test.jar. This contains the scaffolding executables. Do not unzip this jar file, just locate it. I think it should be within a subdirectory named junit.

You can download the XProgramming Testing Framework from junit.org. You should then read the documentation that comes with it. The current version, as I write this, is 3.2.


 

Before we start with testing, examine the following class named OrderItem.java. It is part of a small project that handles order processing.

package orderprocessing;
import java.io.Serializable;
/** This class contains information about a single item on a customer order
* Note that this class implements the Immutable Object design pattern. 
*/
public class OrderItem implements Serializable, Cloneable
{	/** Create an order item for a given quantity of a certain item
	* @param quantity The quantity ordered.
	* @param itemNumber The item desired.
	* @param unitPrice The price per unit for this item. 
	*/
	public OrderItem(int quantity, long itemNumber, double unitPrice)
	{	this.quantity = quantity;
		this.itemNumber = itemNumber;
		this.unitPrice = unitPrice;
	}
	
	public int quantity(){return quantity;}
	public long itemNumber(){return itemNumber;}
	public double unitPrice(){return unitPrice;}
	public double extendedPrice(){return unitPrice * quantity;}
	
	public boolean equals(Object o)
	{	if (!(o instanceof OrderItem)) return false;
		OrderItem oi = (OrderItem)o;
		return quantity == oi.quantity && itemNumber == oi.itemNumber && unitPrice == oi.unitPrice;
	}
	
	public int hashCode(){return (int)((quantity +1) * unitPrice * itemNumber);}
	// Guarantee that two "equals" OrderItems have the same hash code. 
	
	public void display()
	{	System.out.println(this);
	}
	
	public void displayExtended()
	{	System.out.println(quantity + " " + itemNumber + " " + unitPrice * quantity);
	}
	
	public Object clone(){return new OrderItem(quantity, itemNumber, unitPrice);}
	
	public String toString(){ return quantity + " " + itemNumber + " " + unitPrice;}
	
	private int quantity;
	private long itemNumber;
	private double unitPrice;
}

This has an extendedPrice method, an equals method for testing equality, and a hashCode method consistent with equals. Equality testing in this class should be value testing (two OrderItems are equal if they store exactly the same values. This will override the default "reference" equality. The hashCode method must be kept in sync with equals if you wish to store the objects in hash tables as we may at some point.


 

Given the above version of OrderItem, here is the first test program using the JUnit test scaffold.

package orderprocessing;

import junit.framework.*;

public class OrderItemTest extends TestCase // Define a test fixture
{	private OrderItem o123;
	private OrderItem o124;
	
	public OrderItemTest(String s){super(s);} // Always required. Omitted from documentation.
	
	protected void setUp()
	{	o123 = new OrderItem(10, 123, 2.34);
		o124 = new OrderItem(10, 124, 5.67); 
		// Note, we should not do these initializations with the declarations.
		// This method can be called again for each test, making the tests "memoryless" so they
		// don't depend on each other.   
	}
	
	protected void tearDown() throws Exception
	{	super.tearDown(); 
		// Actually nothing is needed here for this example.
		// In general, you may need to release resources (files, say) here.
	}
	
	public void testEquals()//Tests should begin with the word "test"
	{	// assert(!o123.equals(null)); Not needed. The system catches null errors. 
		assertEquals ("OrderItem equality test failed", o123, new OrderItem(10, 123, 2.34));
		assert("OrderItem inequality test failed", !o123.equals(o124));
		// The string will be printed only if the test fails. 
	}
	
	public void testClone()
	{	assertEquals("OrderItem clone test failed", o123, o123.clone());
		assert("OrderItem hashCode test failed", o123.hashCode() == o123.clone().hashCode());
	}
	
	public static Test suite()
	{	TestSuite suite = new TestSuite();
		suite.addTest(new OrderItemTest("testClone"));
		suite.addTest(new OrderItemTest("testEquals"));
		return suite;
	}
	// TestCases and TestSuites implement the Composite desgin pattern. A TestSuite contains
	// TestCases, but it is also, itself, a TestCase. Therefore suites can contain other suites. 
	// These suites naming the tests are optional, actually. You can use reflection to generate them automatically. 

	public static void main(String [] args) // The batch interface: 
	{	junit.textui.TestRunner.run(suite());
	}
	
	/* Usage: Assuming that the junit directory is junit3.7 (at root level),
	the run command for the batch (textual) system here is
	java -cp .;/junit3.7/junit.jar orderprocessing.OrderItemTest
	or make the above class your "main" class in KAWA or other project mgr.	

	Alternatively, to use the GUI interface to the test infrastructure use the batch command
	java -cp .;/junit3.7/junit.jar junit.swingui.TestRunner

	*/
}

To set up Kawa to use this test you must do all of the following.

0. download the junit file and unzip it in a new directory at root level. I'll assume a directory name Project for the working directory and junit3.2 for the junit directory.

1. make a Kawa project, say Test, inside your Project directory.

2. Add all project files and the above file (OrderItemTest.java) to this project.

3. Add the junit.jar file in the Junit3.7 directory to the project also.

4. If you make OrderItemTest.java the main file (right click on the file in left pane and select main class) then you can run these tests within Kawa. OR you can do point 5 instead

5. (alternative to 4) Open a new DOS window, navigate to your Project directory and type the command:

	java -cp .;/junit3.7/junit.jar orderprocessing.OrderItemTest

for the batch system, OR

	java -cp .;/junit3.7/junit.jar junit.swingui.TestRunner

for the GUI system. If you use the GUI system, type

	orderprocessing.OrderItemTest

in the top textfield in the GUI. You can also use the command:

	java -cp .;/junit3.7/junit.jar junit.swingui.TestRunner orderprocessing.OrderItemTest

to start the GUI with the test already specified. I put this commant into a batch (.bat) file and can therefore execute it easily.

 

When I built this class I used a recommended naming style: The name of the class to be tested followed by the word Test: OrderItemTest

As recommended in the Cookbook that comes with the JUnit scaffold, I've declared a few object variables at the beginning to drive the test and created the objects in the setUp method. There is no need for a tearDown method here, but I included it anyway for completeness.

I then defined some tests using public methods and calling the assert and assertEqual methods of the infrastructure. The test names include the name of the method to be tested: testEquals, testClone. These methods have no arguments. I think that may be important. Use instance variables to pass them information if needed.

I then used a static public method named suite (the infrastructure expects this) to define and return a TestSuite named suite. The tests are added to the suite with lines like:

	suite.addTest(new OrderItemTest("testClone"));

Note that this infrastructure is using the java reflection API. The String argument is the name of a method (properly capitalized), and the infrastructure will take this string and use the reflection stuff to find the method with that name and call it.

The main function isn't really needed, but if present will always be the same. It permits you to use this class itself as the target in your java statement, rather than the test.textui.TestRunner class in the jar file.

Below is an additional test class to test the Invoice class of the project. This one isn't complete and you may want to add to it, or even throw out the tests I have here. It is mostly an illustration. Notice, however that I'm putting all of these classes into our package: orderprocessing. This will let us test the non public parts of the class, though testing usually tests the public parts. It won't give us access to private stuff, however.

package orderprocessing;

import junit.framework.*;
import java.util.Vector;
import java.util.Enumeration;

public class InvoiceTest extends TestCase
{	private Invoice i1;
	private Customer c1;
	private Vector items;
	private Order o1;
	
	public InvoiceTest(String s){super(s);} // Always required. Omitted from documentation.
	
	public void setUp()
	{	items = new Vector();
		items.addElement("15~11232~16.95~Widget"); // qty, itemnumber, price, description
		o1 = OrderProcessor.orderProcessor.newOrder
			(	"C5002", 	// customer number
				"NET 30", 	// billing terms
				"default", 	// ship address
				items, 		// items ordered
				new CalculationCalendar(),	// order date (today)
				new CalculationCalendar(),	// ship date (today-will be adjusted to minimums)
				new CalculationCalendar()	// cancel date
			);
		i1 = OrderProcessor.orderProcessor.generateInvoice(o1);
	}
	
	public void tearDown()
	{
	}
	
	public void testItems()
	{	Enumeration e = i1.what();
		assert("Empty Invoice failure", e.hasMoreElements());
		assert("Non-Null invoice item failure", e.nextElement() != null); 
		// Not empty and no null elements.
	}
	
	public static Test suite()
	{	TestSuite suite = new TestSuite();
		suite.addTest(new InvoiceTest("testItems"));
		return suite;
	}
	
	public static void main(String [] args) 
	{	junit.textui.TestRunner.run(suite());
	}
}

 

Below is a class that shows you how to do an integration test, though I don't actually do that here. All I've done is provide a single class to run all of our unit tests. We could add tests to this class to test inter-class dependencies and the like, however. You can define new objects, set them up, test them. Whatever.

package orderprocessing;

import junit.framework.*;

public class SystemTest
{
	public static Test suite()
	{	TestSuite suite = new TestSuite();
		suite.addTest(OrderItemTest.suite()) // Adds ALL of our order item tests.
		suite.addTest(InvoiceTest.suite());	 // Adds ALL of our invoice tests. 
		// Alternatively, you can use reflection to do the above two statements with:
		// suite.addTest(new TestSuite(OrderItemTest.class));
		// suite.addTest(new TestSuite(InvoiceTest.class));
		// This method will include all tests that begin with the word test...
		// This is the preferred method. 
		return suite;
	}
	
	public static void main(String [] args)  
	{	junit.textui.TestRunner.run(suite());
	}	
}

All I really did here was add the suites of the other tests to this suite. To run this make this class your main class or run the GUI version and type in the name orderprocessing.SystemTest. Note that this class doesn't extend TestCase. As such, it can't itself be added to a TestSuite. It is easy to modify so that this is possible, however.

NOTE that TestCases and TestSuites imlement the Composite standard design pattern. TestCases can be added to TestSuites, but TestSuites are themselves TestCases, so you can add one TestSuite to another, bigger, one.


Note that the Wiki Web is an interactive web site that maintains some of the world's best information about object-orientation in general and patterns in particular. It is maintained by Ward Cunningham and Kent Beck , who invented CRC cards. You can, for example, learn more about CRC cards on the site in fact if you do a search.

As you explore the Wiki site keep in mind that the links that you find as you read along on Wiki pages are almost all patterns of different kinds. For example "program in pairs" is an organizational pattern. "Do the simplest thing that could possibly work" is a process pattern, and "composite" (mentioned as the basis of the test schema stuff in XP) is a design pattern. Composite is discussed in GOF and in the Grand book: Patterns in Java.

You can get the latest JUnit version (and equivalent scaffolding for other languages) at the JUnit home page.


JUnit 3.7 update. (Now incorporated into the above) Since I wrote the above, new versions of JUnit have appeared. The most recent is version 3.7. In this version, the assert method has been deprecated (meaning it may not be supported in some future version) and the assertTrue method has been added and suggested for use in place of assert.

You can also add all the tests in a class to a suite provided that their names begin with the word test and they have no arguments. To do this just give the class to the Suite constructor. In our InvoiceTest class this would look like

TestSuite suite = new TestSuite(InvoiceTest.class);

This would be equivalent to constructing it without arguments as we did above and then adding the two tests as we also did. All that is required for this to work is that your test names begin with the word test as in testClone...

Finally, there is a new UI for JUnit that is built with Swing. It has some new features. To use it the command would look like

	java -cp .;/junit3.7/junit.jar junit.swingui.TestRunner

Among other features, this one has a button with which you can select the test to be run from a set of tests that it can see.

Here is a snapshot of the new UI after failing a test. Note that it tells us the file and line at which the failure occurs (InvoiceTest.java, line 42)

Last Updated: October 13, 2002