JaVa
   

Testing Threshold Values and Exceptions

We argued in , , that test cases should concentrate particularly on the boundary areas of inputs and outputs. Provided that we know these threshold values, we can run our tests easily when they are passed to the testing method as parameters. Taking one example, let's assume that our class TextFormatter is ready to be tested. This class is supposed to reformat a passed text line by line. Our boundary condition is that lines with a maximum of 32 characters should be processed; longer lines will be truncated. On these grounds, our test based on threshold values is as follows:

public void testLongLines() {
 TextFormatter formatter = new TextFormatter();
 String line32 = " abcdefg hijklmn opqrs tuvwxy";
 String line33 = " abcdefg hijklmn opqrs tuvwxyz";
 assertEquals("abcdefg hijklmn opqrs tuvwxy",
 formatter.formatLine(line32));
 assertEquals("abcdefg hijklmn opqrs tuvwxy",
 formatter.formatLine(line33));
}



So far, so good. In the course of development the text formatter's responsibility changes. It should not format single lines, but instead complete files and then write the result to a file. This means that its public interface is no longer line-based, but file-based. So we are facing a problem similar to the one we had to solve with the LogServer (see ). Except this time we have to create an input file with corresponding content, in addition to the output file, before the test. If we use a pair of mock classes, one MockLineReader and one MockLineWriter, implementing the LineReader and LineWriter interface, respectively, then writing the test based on the presented mock pattern is easy:

public void testLongLines() {
 MockLineReader reader = new MockLineReader();
 String line32 = " abcdefg hijklmn opqrs tuvwxy";
 String line33 = " abcdefg hijklmn opqrs tuvwxyz";
 reader.addLineToBeRead(line32);
 reader.addLineToBeRead(line33);
 MockLineWriter writer = new MockLineWriter();
 writer.addExpectedLine("abcdefg hijklmn opqrs tuvwxy");
 writer.addExpectedLine("abcdefg hijklmn opqrs tuvwxy");
 TextFormatter formatter = new TextFormatter();
 formatter.format(reader, writer);
 writer.verify();
}


While creating appropriate test files would still be conceivable in this fabricated case, there are other cases where boundary conditions can hardly be handled other than by using dummy or mock objects. For example, think of accessing a server that has a maximum of x seconds to send a reply before the client throws a TimeOutException. How do I get a remote server to wait exactly x-1 or x+1 seconds with its reply to ensure that I can verify the correct response of my client in these boundary cases? A Mock-Server class where I can configure both the desired response and the delay time makes this test child's play. Exceptions are similar to boundary cases. Using the example from , we can modify the interface of our getRateFromTo() method as follows:

public double getRateFromTo(String from, String to)
 throws ServerNotAvailableException;


It may take some persuasion to get our financial information provider to remove the exchange rate server from the network for a few milliseconds whenever we need to do some testing. But with a minor change to the class DummyProvider, we can spare ourselves from such negotiations:

public class DummyProvider extends ExchangeRateProvider {
 private double dummyRate;
 private boolean serverAvailable = true;
 public DummyProvider(double dummyRate) {
 this.dummyRate = dummyRate;
 }
 public double getRateFromTo(String from, String to)
 throws ServerNotAvailableException {
 if (!serverAvailable) {
 throw new ServerNotAvailableException("Test");
 }
 return dummyRate;
 }
 public void setServerAvailable(boolean isAvailable) {
 serverAvailable = isAvailable;
 }
}


We would like our EuroCalculator to use an exchange rate of 1.0 whenever the exchange rate server is not available (we don't want to discuss here whether or not this specification is meaningful ;-). The test for this might look something like this:

public void testServerNotAvailable() {
 //Rate of DummyProvider doesn't matter;
 it will thrown an exception
 DummyProvider provider = new DummyProvider(1.1324);
 provider.setServerAvailable(false);
 double result = new EuroCalculator().valueInEuro(1.5, "USD",
 provider);
 assertEquals(1.5, result, ACCURACY);
}


In this way, we can use mock objects to test for correct behavior in exceptional situations and boundary cases that would otherwise be ignored. But here, too, the following applies: we should beware of bombarding each object with all kinds of exceptions just because we can. For example, if we want to test all places in our program where a NullPointer-Exception could occur, then we would be busy doing only this. Careful balancing between cost and benefit is particularly important in such cases.


JaVa
   
Comments