How can one JUnit test an interactive Java text application?

Ideally, I would like to write JUnit test code that interactively tests I / O applications for text input. Using System.setIn() / .setOut() leads to problems as the underlying threads are blocked. The Birkner system rules ( http://www.stefan-birkner.de/system-rules/index.html ) were recommended in an earlier post ( Console Application / Program Testing - Java ), but it seems that all standard input was provided before the unit test goal was run and therefore was not interactive.

To provide a concrete example of a test goal, consider this guessing game code:

 public static void guessingGame() { Scanner scanner = new Scanner(System.in); Random random = new Random(); int secret = random.nextInt(100) + 1; System.out.println("I'm thinking of a number from 1 to 100."); int guess = 0; while (guess != secret) { System.out.print("Your guess? "); guess = scanner.nextInt(); final String[] responses = {"Higher.", "Correct!", "Lower."}; System.out.println(responses[1 + new Integer(guess).compareTo(secret)]); } } 

Now imagine a JUnit test that will give guesses, read answers and play a game until completion. How can this be done as part of JUnit testing?

Answer:

Using the approach recommended by Andrew Charnsky below, adding reset output (including adding System.out.flush(); after each print statement above), randomly playing and restoring System.in/out, this code seems to run the test I imagined:

 @Test public void guessingGameTest() { final InputStream consoleInput = System.in; final PrintStream consoleOutput = System.out; try { final PipedOutputStream testInput = new PipedOutputStream(); final PipedOutputStream out = new PipedOutputStream(); final PipedInputStream testOutput = new PipedInputStream(out); System.setIn(new PipedInputStream(testInput)); System.setOut(new PrintStream(out)); new Thread(new Runnable() { @Override public void run() { try { PrintStream testPrint = new PrintStream(testInput); BufferedReader testReader = new BufferedReader( new InputStreamReader(testOutput)); assertEquals("I'm thinking of a number from 1 to 100.", testReader.readLine()); int low = 1, high = 100; while (true) { if (low > high) fail(String.format("guessingGame: Feedback indicates a secret number > %d and < %d.", low, high)); int mid = (low + high) / 2; testPrint.println(mid); testPrint.flush(); System.err.println(mid); String feedback = testReader.readLine(); if (feedback.equals("Your guess? Higher.")) low = mid + 1; else if (feedback.equals("Your guess? Lower.")) high = mid - 1; else if (feedback.equals("Your guess? Correct!")) break; else fail("Unrecognized feedback: " + feedback); } } catch (IOException e) { e.printStackTrace(consoleOutput); } } }).start(); Sample.guessingGame(); } catch (IOException e) { e.printStackTrace(); fail(e.getMessage()); } System.setIn(consoleInput); System.setOut(consoleOutput); } 
+6
source share
4 answers

Use PipedInput / OutputStream e.g.

  final PrintStream consoleOutput = System.out; final PipedOutputStream testInput = new PipedOutputStream(); final PipedOutputStream out = new PipedOutputStream(); final PipedInputStream testOutput = new PipedInputStream(out); System.setIn(new PipedInputStream(testInput)); System.setOut(new PrintStream(out)); new Thread(new Runnable() { @Override public void run() { try { PrintStream testPrint = new PrintStream(testInput); BufferedReader testReader = new BufferedReader( new InputStreamReader(testOutput)); while (true) { testPrint.println((int) (Math.random() * 100)); consoleOutput.println(testReader.readLine()); } } catch (IOException e) { e.printStackTrace(consoleOutput); } } }).start(); guessingGame(); 
+3
source

A better approach would be to separate input and game logic.

Create an interface for the input part (using a method of type getNextGuess ) and the specific implementation where you will place your scanner. This way you can also renew or exchange it later. And in your unit tests, you can then mock this class to provide the input you need to test.

+3
source

Perhaps you should ask yourself what code properties you want to provide. For example: you can make sure that the correct answer is given depending on the input. Then you have to reorganize your code and extract a function like

 String getResponse(int secret, int guess) { ... } 

Then you can test

 AssertEquals("Higher.",getResponse(50,51)); AssertEquals("Correct!",getResponse(50,50)); AssertEquals("Lower.",getResponse(50,49)); 

It makes no sense to test the full stream, including random numbers. You can do testloop 0..100, but it’s better to check the lower / upper end and something in between. And you do not need interactive input in unit test. It makes no sense. It can only be lower, higher or equal.

+1
source

I would wrap System.in and System.out in an object that you can then insert. Thus, you can enter the layout in its place in your unit tests. It will also be useful to exchange what you might want to use for input and output in the future! :)

0
source

All Articles