Java Testing Tactics

See also: JavaUnit


Input Requested: How can we make this better? Maybe factor into separate pages? -- EricHerman

Here are some handy tactics that can be used for testing in JavaLanguage.


Swap System.out with a testable alternative

How do you test a program like Hello World:

 public class HelloWorld {
public static void main( String [] args) throws Exception {
 System.out.println("Hello, World.");
}
 }

This is one option:

 import junit.framework.*;
 import junit.runner.BaseTestRunner;
 import java.io.*;

public class HelloWorldTest extends TestCase{

public HelloWorldTest(String name) { super(name); }

public void testMain() throws Exception {

ByteArrayOutputStream testOut = new ByteArrayOutputStream(); PrintStream testOutPrintStream = new PrintStream(testOut); System.setOut(testOutPrintStream);

HelloWorld.main(new String[0]);

/* Be careful of the LineSeparator with println */ String newLine = System.getProperty("line.separator"); if (newLine == null) newLine = "\n";

String expected = "Hello, World." + newLine;

assertEquals("First Assert", expected, testOut.toString());

/* Must reset() between reads! */ assertEquals("Test Same", expected, testOut.toString()); testOut.reset(); assertEquals("Test Empty", "", testOut.toString());

demo.main(new String[0]); assertEquals( "again", expected, testOut.toString()); } }


Swap System.err as well.

The System.err may swapped out just like the System.out. To test this class:

 public class PrintStackTrace {
public static void main( String [] args) throws Exception {
Exception e = new Exception("Print Me");
e.printStackTrace();
}
 }

One option would be:

 import junit.framework.*;
 import junit.runner.BaseTestRunner;
 import java.io.*;

public class PrintStackTraceTest extends TestCase{

public PrintStackTraceTest(String name) { super(name); }

public void testMain() throws Exception {

ByteArrayOutputS'tream testErr = new ByteArrayOutputS'tream(); Print'Stream testErrPrintStream = new Print'Stream(testErr); System.setErr(testErrPrintStream);

PrintStackTrace.main(new String[0]);

assertTrue("1", testErr.toString().startsWith("java.lang.Exception: Print Me")); } }


Swapping System.in

Swapping System.in can be tricky. Here is a basic example:

 import junit.framework.*;
 import junit.runner.BaseTestRunner;
 import java.io.*;

public class SwapInTest extends TestCase{

public SwapInTest(String name) { super(name); }

public void testReplaceSystemIn() throws Exception {

PipedInputStream testInPipe = new PipedInputStream(); PipedOutputStream testOutPipe = new PipedOutputStream(); testInPipe.connect(testOutPipe); System.setIn(testInPipe);

InputStreamReader isr = new InputStreamReader(System.in); BufferedReader reader = new BufferedReader(isr);

String expected = "This is a Test."; String withline = expected + "\n"; testOutPipe.write(withline.getBytes(), 0, withline.length());

assertEquals("Test One", expected, reader.readLine()); } }

Of course, Reading and Writing from the same thread is risky: we open ourselves up to deadlock problems. If our tests fail, we want them to fail correctly, not just hang in an infinite loop. For a more robust test, we'll need to look into threads. Luckily, (unlike keyboard and console I/O which java makes hard) threading in java is fairly easy.


How to test Console Keyboard input?

Even with easy threading options, the problem is still a hard one. I'm still working on this in my own head, and I welcome any input. In the prior example, we were careful to add a newline ('\n') character before performing a call to the readline() method. Without that newline, readline will never return. This isn't so bad in a test where we can see at a glance that we are providing usable data and using acceptably it. In most cases, the call to read input will not be in the test but rather in the class that the test is testing. How do we test a class like this:

 import java.io.*;

public class HelloYou {

public static void main( String [] args) throws Exception { InputStreamReader isr = new InputStreamReader(System.in); BufferedReader inStream = new BufferedReader(isr);

System.out.print("What is your name? "); String name = inStream.readLine(); System.out.println("Hello, " + name + ".");

}

Sure, we can only write tests that stock System.in with newline characters at the end, but that doesn't seem like a very good solution.

Console keyboard input is especially hard to write tests for because of the deadlock issues. Should we spawn a new thread in our test to supply fake user input Should we try to extend our testing framework to kill off a test if it takes more than "x" amount of time to complete? MartinFowler has suggested that the most important criterion for software design is testability. Perhaps, we get the same functionality out of the HelloYou class with a better design that won't hang our test?

Perhaps our program under test could spin off a separate thread to "listen" for input, not blocking the main thread of execution.

What are your thoughts?

-- EricHerman


See also:

http://showell.westhost.com/cgi-bin/XpSeattleWiki.pl?InteractiveTestingInJava (BrokenLink 2006-01-07)


EditText of this page (last edited January 7, 2006) or FindPage with title or text search