0

Below is the java program to guess the number of Apples Tom has. Tom would type higher if he has higher number of apples than the guessed number and lower if it is lower. He will respond with none when the number guessed is correct. My task is to write a JUnit test case for this program. Since based on the user input the guessed number is either decremented or incremented until we reach the actual value it confuses me as to how to write a JUnit test case for this program

  public static void main(String args[]){
      System.out.println("Guess the number of Apples Tom has");
      Scanner sc= new Scanner(System.in);
      int number=sc.nextInt();
      System.out.println("Tom, is it higher or lower?");
      String higherOrLower=sc.nextLine();

      while(true){
          number= getValue(higherOrLower,number);
          System.out.println("Tom, is it higher or lower?");
          higherOrLower=sc.nextLine();
          if(higherOrLower.equalsIgnoreCase("none")) {
                break;
          }
      }
  }

  public static int getValue(String i,int j){

        if(i.equalsIgnoreCase("lower")) {
            j--;
            return j;
        } else if(i.equalsIgnoreCase("higher")) {
            j++;
            return j;
        } else {
            return j;
        }
    }
arsenics
  • 13
  • 1
  • 6
  • There's also this one: http://stackoverflow.com/questions/15077312/junit-testing-around-sytem-in-and-system-out –  Feb 23 '16 at 06:11
  • You have some ';' missing (e.g. line 5 and 10). Please correct the code. – Radu Dumbrăveanu Feb 23 '16 at 06:31
  • In your case, you already have a nice method "getValue" that you can test independently. Write different tests for various possibilities (higher, lower, etc.) and check if the return value is correct. For the actual user input, there already have been some pointers to system.in testing. – Florian Schaetz Feb 23 '16 at 06:40
  • @FlorianSchaetz To answer your first question getValue can only be tested to see if it is returning a lower value when the input string "i" is lower or a higher value when "i" is higher. For the second suggestion that you gave,in this scenario the user input depends on what the getValue method returns(i.e., higher, lower than the number of Apples). The existing example on system.in testing explains how I can inject independent user input i.e., user input is randomly given to arrive at a solution. There is a difference, isn't it? – arsenics Feb 23 '16 at 07:15
  • Make `Scanner` and `System.out` dependencies and use dependency injection. In your test, inject mocks and assert on certain interactions with these mocks ([mockito testing framework](http://mockito.org/)). – Jaroslaw Pawlak Feb 23 '16 at 07:18

1 Answers1

0

Ok, let's go into a bit of detail here...

The basic for a good unit test is good code. Which explains why writing unit tests doesn't only make code work more reliable, it also tends to make the coders write better code (because bad code if very often hard to test).

In your example you have two methods (I assume you aren't into the details of Object Oriented Programming (OOP) yet, so I will not go into how to structure your code even better with additional classes).

One method does the actual number crunching (ok, little bit crunching), the other does the input. Unfortunately, your input method is also your main method, so you could do that a little better, since main methods are not very good for testing.

public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in); // We'll ignore finer problems with scanners, etc. here
    playGame(scanner);

}

public static void playGame(Scanner scanner) {
    System.out.println("Guess the number of Apples Tom has");
    Scanner sc= new Scanner(System.in);
    int number=sc.nextInt();
    System.out.println("Tom, is it higher or lower?");
    String higherOrLower=sc.nextLine();

    while(true){
        number= getValue(higherOrLower,number);
        System.out.println("Tom, is it higher or lower?");
        higherOrLower=sc.nextLine();
        if(higherOrLower.equalsIgnoreCase("none")) {
            break;
        }
    }
}

This little change will make your code much more testable. Let's start with the easy part, the getValue method: @Test public void lower_should_return_number_minus_1() { int result = getValue("lower", 10); Assert.assertEquals(9, result); // simple assertions }

@Test
public void higher_should_return_number_plus_1() {
    int result = getValue("higher", 10);
    Assert.assertEquals(11, result); // simple assertions
}

@Test
public void garbage_should_return_same_number() {
    int result = getValue("Hello, Polly!", 10);
    Assert.assertEquals(10, result); // simple assertions
}

This will test most of your possibilities. It would be a good idea to also test what happens if you enter null for a String, just to be sure ;-)

Testing the other method will be a little bit harder, since it involves a little tricking...

@Test(timeout=1000)
public void game_should_work_on_input() {
    final ByteArrayInputStream stream = new ByteArrayInputStream(String.format("5%nlower%nlower%nnone%n").getBytes());
    final Scanner scanner = new Scanner(stream);
    playGame(scanner);
}

Here we simply create a "fake" input, that consists of "5", "lower", "lower" and "none" (honestly, there's a blank line between 5 and the first lower). The String.format will add the correct new line char(s) for each %n. If your test succeeds, it means the game was played to the end. If not, a timeout will happen auf 1000ms (which means your game did not end correctly, which it should). Didn't test it, honestly, but you can go from there, I'm sure. ;-)

Florian Schaetz
  • 10,454
  • 5
  • 32
  • 58
  • Thanks!. I was able to write few test cases to check the getValue method. But the game_should_work_on_input() test case would fail if the solution is not found in 1000ms which possibly can happen. But that doesn't mean the code is not working properly. So it won't be a valid fail scenario, Isn't it?. Appreciate the help – arsenics Feb 23 '16 at 15:41
  • For THAT scenario, 1000ms should be more than enough time. This testcase is "The game should end in less than 1000ms if the correct solution is found after the inputs "5", "lower", "lower" and "none". Other testcases might take longer, of course, but that one only consists of 3 "moves" and should be done in less than 1000ms. – Florian Schaetz Feb 23 '16 at 17:35