使用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的概念非常酷,值得在互联网上阅读。 它有助于开发出良好的线程安全可测试代码。