Thursday, October 25, 2007

Unit Testing (1): Overview

First article of a series: Use unit testing with open source Java components, in the environment of Swing UIs, J2EE server components, database access (esp. hibernate), web applications, etc. Unit testing is an important part of Test Driven Development (TDD). Admittedly, I have mostliy ignored this topic up to now. Obviously, the overview I have over my own code was enough to detect, place and correct bugs; working with others' code, detecing errors was usually easy, leaving them to fix it ;-) Also, I was always of the opinion that "my" projects were not applicable for Unit Testing ... complicated UIs, enterprise architectures, complex databases, J2EE deployment, etc. Luckily, others have worked on developing frameworks for most of these purposes.

Test-Driven Development

First write the tests, then the structure of the class to implemented, then run the tests. They'll fail, as there is no implementation yet. So, do the coding, test it regularly, until the tests don't fail any longer ... finshed, sort of.

JUnit

JUnit is the most-used Java testing framework. There are so many good tutorials out there, so I don't see the need to write another one. Read one or more of
  • http://www.junit.org/
  • http://junit.sourceforge.net/
  • http://www.ibm.com/developerworks/library/j-ant/
  • http://www.torsten-horn.de/techdocs/java-junit.htm
In the following, I will use JUnit 4.4. Version 4 changed a lot of the usage of Test Cases and such, using Java 5 annotations.
  • http://www.frankwestphal.de/JUnit4.0.html
  • http://www.mm.informatik.tu-darmstadt.de/courses/helpdesk/junit4.html
  • http://radio.javaranch.com/lasse/2006/07/27/1154024535662.html
  • http://www.instrumentalservices.com/content/view/45/52/
I will also make use of the assertThat statement which was introduced with JUnit 4.4, because it makes much of the test code more readable, especially the error messages.
  • http://junit.sourceforge.net/doc/ReleaseNotes4.4.html

Sample

We write a simple utility for concatenating the textual representation of list elements, with a user-defined concatenation string. Having the list ("Hello", "world", "!"), CollectionUtils.collectionToString(list, ", ") should produce "Hello, World, !".

Unit Test for collectionToString

We start with the unit test.
public class  CollectionUtilsTest {
@Test public void testCollectionToString() {
 final List <String> list1 = Arrays.asList("Hello", "World", "!");
 final String comma = ", ";
 final String result1 = CollectionUtils.collectionToString(list1, comma);
 // we can check the complete string
 assertEquals("Hello, World, !", result1);
 // or the items
 for (String s: list1)
   assertThat(result1, containsString(s));
 // and, all items except that last, need to be followed by ", "
 Iterator  it = list1.iterator();
 while (it.hasNext()) {
   String s = it.next();
   if (it.hasNext())
     assertThat(result1, containsString(s+comma));
 }
}
}
assertEquals() is quite easy to understand, imported via import static org.junit.Assert.assertEquals, and compares its two parameters for equality. Usually, a first parameter should be added that contains the message to print, if the two values are not equal.

Code for collectionToString()

Of course, we also need the CollectionUtils class.
public class CollectionUtils {
// for the moment, we don't do anything
public static String collectionToString(List<?> list, String comma) {
 return null;
}
That's enough to run a unit test ... with eclipse, you just need to start either the test case class (CollectionUtilsTest above) with "Run as JUnit Test", or -- alternatively -- the whole project, which runs all unit tests in the selected package.

First test run

java.lang.AssertionError: expected:<Hello, World, !> but was:<null<
...
  at sbr.jut.demo.CollectionUtilsTest.testCollectionToString(CollectionUtilsTest.java:29)
...

Coding collectionToString

public static String collectionToString(Collection coll, String comma) {
StringBuilder sb = new StringBuilder();
String sComma = "";
for (String s: coll) {
  sb.append(sComma);
  sb.append(s);
  sComma = comma;
}
return sb.toString();
}
And, now, the unit test runs through ... until we start some demonic testing ;-)

Special test cases

Of course, null pointers and stuff need to be taken care of, especially ...
assertEquals("collectionToString(null, null)", "",
CollectionUtils.collectionToString(null, null));
assertEquals("collectionToString(null, null)", "",
CollectionUtils.collectionToString(Collections.EMPTY_LIST, null));
final String result2 = CollectionUtils.collectionToString(list1, "");
assertEquals("HelloWorld!", result2);
final String result3 = CollectionUtils.collectionToString(list1, null);
assertEquals("HelloWorld!", result3);
Nice, we start with the first NullPointerException
java.lang.NullPointerException
  at sbr.jut.demo.CollectionUtils.collectionToString(CollectionUtils.java:18)
  at sbr.jut.demo.CollectionUtilsTest.testCollectionToString(CollectionUtilsTest.java:45)
...
, start fixing our implementation, until we end with ... no NPE!
public static String collectionToString(Collection coll, String comma) {
if (coll==null)
  return "";
if (comma==null)
  comma = "";
StringBuilder sb = new StringBuilder();
...
}
So, that is enough for the beginning ...

No comments: