4.1. An Introduction to Automated Testing
One of the most widely used testing toolkits is called JUnit, and it will be introduced shortly. Before getting to JUnit, it is worth considering the motivation behind the development of such a tool.A developer initially testing a program is likely to do something similar to what was done with the factorial program developed in Chapter 3. That example was tested by running the program with a "typical" input and making sure it worked. We will examine what constitutes a good set of inputs to test against soon, but for now just consider the process by which testing is done.After repeating this kind of test many times, it may occur to the developer that one of the things computers are good at is performing time-consuming, repetitive tasks so that humans don't need to. This suggests writing a second program to test the first, which might be done as in Listing 4.1.
Listing 4.1. A simple automated tester
Listing 4.1 is certainly not the simplest possible test program. It would have been possible to run the test within main() or to make testFact() static and thus avoid the constructor. These shortcuts were avoided to treat TestFact as a full-fledged Java program.It is straightforward to derive more sophisticated test programs from Listing 4.1 One possibility would be to make the test more comprehensive by testing the behavior when given a negative number. This is a classic example of the kind of test that might often be overlooked. A developer who was asked to write a factorial program is likely to know that the factorial function is only defined for positive integers and so would not think to test a negative value. However, an end user might be curious as to what the factorial of a negative number is, or a more complex program might compute some intermediate value that could be negative and then use Fact to compute the factorial of that value.Adding such a test can most easily be accomplished by adding another method to FactTest that will closely resemble testFact(). The only question is what value should be compared against the result of getFactorial() to determine whether the test succeeded.As written, getFactorial() will return 1 when given a negative value. This is wrong in the worst possible way because it produces an answer that looks reasonable but is meaningless. A better behavior would be for Factorial to explicitly reject negative values, which can be accomplished by having setNum() throw an IllegalArgumentException when called with a negative value. A few additional steps will need to be made to accommodate this change. The result is shown in Listing 4.2. Notice how considerations about how a program should be tested can in turn affect how the code is written, which in turn makes that code more robust.
package com.awl.toolbook.chapter04;
import com.awl.toolbook.chapter03.Factorial;
public class FactTest {
public void testFact() {
Factorial f = new Factorial(10);
if(f.getFactorial() == 3628800) {
System.out.println("Test succeeded");
} else {
System.out.println("Test failed");
}
}
public static void main(String argv[]) {
FactTest ft = new FactTest();
ft.testFact();
}
}
Listing 4.2. The revised Factorial class
With Factorial suitably modified the second test can be written and main() modified to invoke it. The new code is shown in Listing 4.3.
/*
* Created on Jun 23, 2003
*
* To change the template for this generated file go to
* Window>Preferences>Java>Code Generation>Code and
* Comments
*/
package com.awl.toolbook.chapter04;
/**
*
* To change the template for this generated type comment
* go to
* Window>Preferences>Java>Code Generation>Code and
* Comments
*/
public class Factorial {
private int num;
public void setNum(int n)
throws IllegalArgumentException
{
if(n < 1) {
throw new IllegalArgumentException(
"Factorial is only defined for pOSItive integers");
}
num = n;
}
public Factorial() {
try {
setNum(1);
} catch (IllegalArgumentException e) {}
}
public Factorial(int n)
throws IllegalArgumentException
{
setNum(n);
}
public int getFactorial() {
int total = 1;
for(int i=1;i<num+1;i++) {
total = total * i;
}
return total;
}
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
Factorial f = new Factorial(n);
System.out.println(f.getFactorial());
}
}
Listing 4.3. A second test
public void testFactNegative() {
try {
Factorial f = new Factorial(-1);
System.out.println("Test failed" );
} catch (IllegalArgumentException e) {
System.out.println("Test succeeded");
}
}
public static void main(String argv[]) {
FactTest2 ft = new FactTest2();
ft.testFact();
ft.testFactNegative();
}