Search code examples
javajunitsystem.in

Writing a test case for a Java program which takes user input in iterations to get to a final value


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;
        }
    }

Solution

  • 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. ;-)