使用模拟用户输入进行JUnit测试

Wim*_*pey 72 java eclipse junit user-input

我正在尝试为需要用户输入的方法创建一些JUnit测试.测试中的方法看起来有点像以下方法:

public static int testUserInput() {
    Scanner keyboard = new Scanner(System.in);
    System.out.println("Give a number between 1 and 10");
    int input = keyboard.nextInt();

    while (input < 1 || input > 10) {
        System.out.println("Wrong number, try again.");
        input = keyboard.nextInt();
    }

    return input;
}
Run Code Online (Sandbox Code Playgroud)

是否有可能在JUnit测试方法中自动将程序传递给int而不是我或其他人手动执行此操作?像模拟用户输入一样?

提前致谢.

Krz*_*zyH 91

您可以通过调用System.setIn(InputStream in)System.in替换为您自己的流.输入流可以是字节数组:

InputStream sysInBackup = System.in; // backup System.in to restore it later
ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes());
System.setIn(in);

// do your thing

// optionally, reset System.in to its original
System.setIn(sysInBackup);
Run Code Online (Sandbox Code Playgroud)

通过传入IN和OUT作为参数,不同的方法可以使这种方法更具可测性:

public static int testUserInput(InputStream in,PrintStream out) {
    Scanner keyboard = new Scanner(in);
    out.println("Give a number between 1 and 10");
    int input = keyboard.nextInt();

    while (input < 1 || input > 10) {
        out.println("Wrong number, try again.");
        input = keyboard.nextInt();
    }

    return input;
}
Run Code Online (Sandbox Code Playgroud)

  • @KrzyH我将如何通过多个输入执行此操作?假设在//做你的事我有三个用户输入提示.我该怎么办呢? (5认同)
  • 如何模拟按Enter键?现在,程序只是一次性读取所有输入,这是不好的.我试过`\n`但这并没有什么区别 (5认同)
  • @CodyBugstein使用=新的ByteArrayInputStream((“” 1 + System.lineSeparator()+“ 2”)。getBytes());中的ByteArrayInputStream来获取不同的keyboard.nextLine()调用的多个输入。 (2认同)

Gar*_*all 16

要测试驱动代码,您应该为系统输入/输出函数创建一个包装器.你可以使用依赖注入来实现这一点,为我们提供一个可以请求新整数的类:

public static class IntegerAsker {
    private final Scanner scanner;
    private final PrintStream out;

    public IntegerAsker(InputStream in, PrintStream out) {
        scanner = new Scanner(in);
        this.out = out;
    }

    public int ask(String message) {
        out.println(message);
        return scanner.nextInt();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以使用模拟框架为我的函数创建测试(我使用Mockito):

@Test
public void getsIntegerWhenWithinBoundsOfOneToTen() throws Exception {
    IntegerAsker asker = mock(IntegerAsker.class);
    when(asker.ask(anyString())).thenReturn(3);

    assertEquals(getBoundIntegerFromUser(asker), 3);
}

@Test
public void asksForNewIntegerWhenOutsideBoundsOfOneToTen() throws Exception {
    IntegerAsker asker = mock(IntegerAsker.class);
    when(asker.ask("Give a number between 1 and 10")).thenReturn(99);
    when(asker.ask("Wrong number, try again.")).thenReturn(3);

    getBoundIntegerFromUser(asker);

    verify(asker).ask("Wrong number, try again.");
}
Run Code Online (Sandbox Code Playgroud)

然后编写通过测试的函数.该函数更清晰,因为您可以删除询问/获取整数重复并封装实际的系统调用.

public static void main(String[] args) {
    getBoundIntegerFromUser(new IntegerAsker(System.in, System.out));
}

public static int getBoundIntegerFromUser(IntegerAsker asker) {
    int input = asker.ask("Give a number between 1 and 10");
    while (input < 1 || input > 10)
        input = asker.ask("Wrong number, try again.");
    return input;
}
Run Code Online (Sandbox Code Playgroud)

对于你的小例子来说,这似乎有些过分,但如果你正在构建一个更大的应用程序,这样的开发可以很快得到回报.


Jef*_*ica 5

测试类似代码的一种常用方法是提取一个接收Scanner和PrintWriter的方法,类似于此StackOverflow答案,并测试:

public void processUserInput() {
  processUserInput(new Scanner(System.in), System.out);
}

/** For testing. Package-private if possible. */
public void processUserInput(Scanner scanner, PrintWriter output) {
  output.println("Give a number between 1 and 10");
  int input = scanner.nextInt();

  while (input < 1 || input > 10) {
    output.println("Wrong number, try again.");
    input = scanner.nextInt();
  }

  return input;
}
Run Code Online (Sandbox Code Playgroud)

请注意,在结束之前您将无法读取输出,并且您必须预先指定所有输入:

@Test
public void shouldProcessUserInput() {
  StringWriter output = new StringWriter();
  String input = "11\n"       // "Wrong number, try again."
               + "10\n";

  assertEquals(10, systemUnderTest.processUserInput(
      new Scanner(input), new PrintWriter(output)));

  assertThat(output.toString(), contains("Wrong number, try again.")););
}
Run Code Online (Sandbox Code Playgroud)

当然,您可以将"扫描仪"和"输出"保留为被测系统中的可变字段,而不是创建重载方法.我倾向于将课程视为无国籍,但如果对你或你的同事/导师来说这不是很大的让步.

您也可以选择将测试代码放在与测试代码相同的Java包中(即使它位于不同的源文件夹中),这样您就可以放宽对两个参数重载的可见性,使其成为package-private.