使用Scanner对用户输入进行junit测试

我必须在类中测试一个方法,该方法使用Scanner类进行输入。

package com.math.calculator; import java.util.Scanner; public class InputOutput { public String getInput() { Scanner sc = new Scanner(System.in); return sc.nextLine(); } } 

我想使用JUnit测试它,但不知道如何做到这一点。

我尝试使用以下代码,但它不会工作。

 package com.math.calculator; import org.junit.Test; import static org.junit.Assert.assertEquals; public class InputOutputTest { @Test public void shouldTakeUserInput() { InputOutput inputOutput= new InputOutput(); assertEquals("add 5", inputOutput.getInput()); } } 

我也想和Mockito一起尝试(使用模拟……当…然后返回)但不知道该怎么做。

您可以使用System.setIn()方法更改System.in流。

尝试这个,

 @Test public void shouldTakeUserInput() { InputOutput inputOutput= new InputOutput(); String input = "add 5"; InputStream in = new ByteArrayInputStream(input.getBytes()); System.setIn(in); assertEquals("add 5", inputOutput.getInput()); } 

您刚刚修改了System.in字段。 System.in基本上是一个从console读取的InputStream (因此在控制台中输入)。 但是你只是修改了它,让系统从提供的输入inputstream读取。 所以它不再从控制台读取,而是从提供的输入流中读取。

除了切换System.in之外 ,正如Codebender所提到的,考虑重构,因此getInput()成为您编写的彻底getInput(Scanner)方法的单行调用,您可以通过创建自己的Scanner("your\ntest\ninput\n")轻松测试Scanner("your\ntest\ninput\n") 。 有许多其他方法可以注入您的扫描仪依赖项,例如将一个字段覆盖以进行测试,但只是使方法过载非常容易,并且在技术上为您提供了更大的灵活性(让您添加一个function来读取文件中的输入,例如)。

一般来说,请记住设计便于测试,并比低风险部件更严格地测试高风险部件。 这意味着重构是一个很好的工具,测试getInput(Scanner)可能比测试getInput()更重要,特别是当你不仅仅调用nextLine()

我建议大量反对创建一个模拟扫描程序:模拟一个你不拥有的类型不仅是不好的做法,而且Scanner代表了一个非常大的相关方法的API,其中调用顺序很重要。 要在Mockito中复制它意味着您要么在Mockito中创建一个大的伪造的Scanner实现,要么模拟一个仅测试您所做的调用的最小实现(如果您的实现发生更改,则会中断,即使您的更改提供了正确的结果)。 使用真正的扫描仪并保存Mockito练习以进行外部服务调用或您正在模拟您定义的小型未写入API的情况。

您可以使用System Rules库的TextFromStandardInputStream规则为命令行界面编写清晰的测试。

 public void MyTest { @Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Test public void shouldTakeUserInput() { systemInMock.provideLines("add 5", "another line"); InputOutput inputOutput = new InputOutput(); assertEquals("add 5", inputOutput.getInput()); } } 

首先,我假设您的测试目标是validation是否从扫描仪获得了用户输入,并且返回的值是扫描仪中输入的值。

模拟不起作用的原因是因为您每次都在getInput()方法中创建实际的扫描程序对象。 因此,无论你做什么,你的mockito实例都不会被调用。 因此,使该类可测试的正确方法是识别该类的所有外部依赖项(在本例中为java.util.Scanner并通过构造函数将它们注入到类中。这样您可以在注入模拟Scanner实例期间注入测试。这是dependency injection的基本步骤,这反过来会导致良好的TDD。一个例子可以帮助你:

  package com.math.calculator; import java.util.Scanner; public class InputOutput { private final Scanner scanner; public InputOutput() { //the external exposed default constructor //would use constructor-chaining to pass an instance of Scanner. this(new Scanner(System.in)); } //declare a package level constructor that would be visible only to the test class. //It is a good practice to have a class and it's test within the same package. InputOutput(Scanner scanner) { this.scanner = scanner; } public String getInput() { return scanner.nextLine(); } } 

现在你的测试方法:

 @Test public void shouldTakeUserInput() { //create a mock scanner Scanner mockScanner = mock(Scanner.class); //set up the scanner when(mockScanner.nextLine()).thenReturn("add 5"); InputOutput inputOutput= new InputOutput(mockScanner); //assert output assertEquals("add 5", inputOutput.getInput()); //added bonus - you can verify that your scanner's nextline() method is //actually called See Mockito.verify verify(mockScanner).nextLine(); } 

另请注意,因为在上面的类中我使用构造函数进行注入,所以我已声明Scanner实例为final。 由于我在这个类中没有更多可变状态,因此该类是线程安全的。

基于构造函数的dependency injection的概念非常酷,值得在互联网上阅读。 它有助于开发出良好的线程安全可测试代码。