12.10. Writing JUnit Tests¶
12.10.1. Writing JUnit Tests¶
We have reviewed good design for test code and how to design modular and meaningful test code. This tutorial will show a basic series of tests on a binary search tree.
Before you start, be sure you have familiarized yourself with the basics of the
Junit/student.TestCase classes
.
For this exercise I will be using a BST written by a V.S. Adamchik
Here is the full BST source code.
While this class contains many methods this tutorial will only be testing a few.
import student.TestCase;
public class TestBST extends TestCase{
private BST<Integer> bst;
public void setUp() {
bst = new BST<Integer>();
}
public void testAdd() {
bst.insert(new Integer(8));//We see this tree on the Wikipedia as an example of a bst
bst.insert(new Integer(3));//We will use it to test our insert function since we
bst.insert(new Integer(1));//know how the traversal at the end should work. There
bst.insert(new Integer(6));//are other ways to do this, however, this structure should
bst.insert(new Integer(4));//provide us not only with a series of inserts that will hit
bst.insert(new Integer(7));//every line of code in the insert method and produce results
bst.insert(new Integer(10));//that we can expect
bst.insert(new Integer(14));
bst.insert(new Integer(13));
bst.insert(new Integer(13));//Handle inserting an element that already exists
bst.preOrderTraversal();
assertFuzzyEquals("8 3 1 6 4 7 10 14 13", systemOut().getHistory());
//systemOut().getHistory() returns all information printed to the terminal
//We use fuzzy equals as there may be leading/following whitespace and
//we do not want to have to work to make a direct match
systemOut().clearHistory();//Clear the history so the next time we print
//so we do not have to worry about the past output.
bst.inOrderTraversal();
assertFuzzyEquals("1 3 4 6 7 8 10 13 14", systemOut().getHistory());
}
public void testRemove() {
Exception d = null;
try{
bst.delete(new Integer(1));//It possible to throw an exception if
//deleting on an empty tree
} catch(Exception e) {
d = e;
assertEquals(e.getMessage(), "cannot delete.");
assertEquals(e.getClass(), RuntimeException.class);
//There are a number of ways to test exceptions
//One way would be to get the message that it prints, however, that
//message will likely change in most exceptions (i.e. FileNotFound will
//throw information about the file location). The other way to test an
//exception would be to check the type of exception thrown. In addition,
//it is wise to make sure an that the catch block is reached by setting
//a marker value to guarantee it has been reached.
}
assertNotNull(d);//Make sure an exception was thrown
String tree = "";
for(int i=10; i < 20; i++) {
bst.insert(new Integer(i));
tree += i+" ";
}
for(int k=9; k > -1; k--) {
bst.insert(new Integer(k));
tree = k+" "+tree;
}
for(int j=0; j < 10; j++) {
bst.delete(new Integer(j));//Test basic delete functionality
tree = tree.replaceFirst(Integer.toString(j), "");
systemOut().clearHistory();//Clear system so we only haver current tree
bst.inOrderTraversal();//After each removal
assertFuzzyEquals(tree, systemOut().getHistory());//See if the tree is what we expect
}
for(int l=19; l > 9; l--) {
bst.delete(new Integer(l));
tree = tree.replaceFirst(Integer.toString(l), "");
systemOut().clearHistory();
bst.inOrderTraversal();
assertFuzzyEquals(tree, systemOut().getHistory());
}
bst.insert(new Integer(10));//Handle the edge cases of deletion
bst.insert(new Integer(8));//Deleting a leaf
bst.insert(new Integer(9));
bst.insert(new Integer(6));
bst.insert(new Integer(7));
bst.delete(new Integer(6));
systemOut().clearHistory();
bst.inOrderTraversal();
assertFuzzyEquals("7 8 9 10", systemOut().getHistory());
bst.insert(new Integer(6));//Deleting an internal node
bst.delete(new Integer(7));
systemOut().clearHistory();
bst.inOrderTraversal();
assertFuzzyEquals("6 8 9 10", systemOut().getHistory());
bst = new BST<Integer>();
bst.insert(new Integer(10));//Deleting an internal node and pushing
bst.insert(new Integer(8));//the new node up
bst.insert(new Integer(9));
bst.insert(new Integer(7));
bst.delete(new Integer(8));
systemOut().clearHistory();
bst.inOrderTraversal();
assertFuzzyEquals("7 9 10", systemOut().getHistory());
bst = new BST<Integer>();
bst.insert(new Integer(10));//Deleting an internal node and progressing
bst.insert(new Integer(8));//down the left subtree to the rightmost
bst.insert(new Integer(9));//node found in it.
bst.insert(new Integer(6));
bst.insert(new Integer(7));
bst.delete(new Integer(8));
systemOut().clearHistory();
bst.inOrderTraversal();
assertFuzzyEquals("6 7 9 10", systemOut().getHistory());
}
}
The above source code shows a possible approach to testing the BST class add and delete methods. Given that a binary search tree is a well documented data structure, there are a number of sites one may refer to for information to test. In this case I referred to the wikipedia entry and pulled the first example of a binary search tree available. Those familiar with tree traversals know that for a given set of values an in order traversal will display the values in order from least to greatest, so after adding all the values to the tree, it is simple to know what to expect. In order to guarantee that the tree has been properly constructed I have chosen to test the Pre-Order traversal as well. Testing the delete function is a bit tougher. There are several cases that must be considered in order to get proper code coverage namely: deleting a leaf node, deleting an internal node with one child, deleting an internal node with 2 children, and deleting an internal node which does not immediately link to a leaf node. In addition to all of this examination of the delete function shows that it is possible for that function to throw a RuntimeException. In order to test this the test code attempts force the delete function to throw the exception, however, it is possible that the exception will not be thrown and no actual test on the Exception could be performed. As such the test code makes use of a canary value to guarantee that that the exception is thrown. Or else the tests will fail. After testing the Exception, the function then tries to test the series of cases that the delete function has.
In many cases testing for equality will satisfy the what is needed to properly test code. However not all information may be tested for strict equality as data representations may not always provide exact information. For example when working with floats or doubles it is important to test the results, however, doing so for strict equality is no simple task. By making use of additional functional of assertEquals method it is possible ot assign an acceptable threshold of difference between two values consider the below code.
public void testsquareroot(){
assertEquals(Calculator.squareroot(2), 1.4142, .001);
assertEquals(Calculator.squareroot(2), 1.4142, .000000000001);
}
The first test would succeed, but the second will fail. Why? Well in the second test the threshold is much finer and Java will fail as 1.4142 is not close enough to 1.4142135623730951. Keep this information in mind as you develop code and choose data types.