21 May 2012

How to automatically test Java Console

Some weeks ago, during a workroom lesson in University, I've faced a typical TDD-addicted dilemma: how can I test driven develop a console-based Java application?
The main problem is clearly how to automatically interact with the application, which relies to System.in for user input and to System.out for user output.
You can use System.setIn and System.setOut methods, of course: but this is IMHO a dirty solution to the console-interaction testability problem, which can be resolved in a cleaner way referring to the dependency inversion principle, ubiquitous in test driven design: rather than directly referring to concrete System.in and System.out streams (this reference is concrete because it's direct, not because it points to a concrete class: InputStream is really an abstract class), the console-based application should reference to some abstraction that encapsulates standard I/O streams dependency - for example to Scanner (for user input) and PrintStream (for user ouput: again, direct reference to System.out is concrete because it's direct, not because it points to something concrete).
So, application behaviour can be encapsulated into a classe having a constructor like this:
 
public HelloApp(Scanner scanner, PrintStream out)

The application main method instances such a class and invokes a method thar triggers application logic execution, simply providing Scanner and PrintStream that wrap standard I/O streams:

public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        scanner.useDelimiter(System.getProperty("line.separator"));
        
        HelloApp app = new HelloApp(scanner, System.out);
        app.run();
}

Testing code, however, can provide to HelloApp testing oriented instances of Scanner and PrintStream:

final Scanner scanner = new Scanner("Duke y Goofy y Donald n");
scanner.useDelimiter(" "); 
 
ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
PrintStream out = new PrintStream(outputBuffer); 
 
final HelloApp app = new HelloApp(scanner, out);
app.run();
 
final String output = outputBuffer.toString(); 
// Assertions about outputBuffer content:
assertTrue(output.startsWith("Welcome to HelloApp!")); 

So, we have gracefully decoupled application logic from console-based user interactions, providing a solid framework for automated application testing and, even more satisfying for TDD addicted, for Test Driven Development.


Code repository can be cloned using git:
git clone https://bitbucket.org/pietrom/automatically-testing-the-console.git

No comments:

Post a Comment