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

时间:2015-07-26 10:00:06

标签: java junit mocking mockito

我必须在类中测试一个方法,该方法使用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一起尝试(使用模拟......当...然后返回)但不知道该怎么做。

4 个答案:

答案 0 :(得分:14)

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

试试这个,

@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基本上是InputStream,它从console读取(因此您在控制台中输入)。但您只是修改了它,让系统从提供的inputstream中读取。所以它不再从控制台读取,而是从提供的输入流中读取。

答案 1 :(得分:3)

除了switching System.in之外,正如Codebender所提到的那样,考虑重构,以便getInput()成为您编写的全面getInput(Scanner)方法的单行调用,您可以通过创建轻松测试你自己的Scanner("your\ntest\ninput\n")。还有许多其他方法可以注入您的扫描仪依赖项,比如让您为测试覆盖一个字段,但只是让方法过载非常简单,技术上会给您更大的灵活性(让您添加一个功能来读取文件中的输入,例如)。

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

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

答案 2 :(得分:2)

您可以使用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());
  }
}

答案 3 :(得分:0)

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

您进行模拟的原因不起作用的原因是您每次都在getInput()方法中创建实际扫描程序对象。因此,无论你做什么,你的模拟实例都不会被调用。因此,使这个类可测试的正确方法是识别类的所有外部依赖项(在本例中为java.util.Scanner并通过构造函数将它们注入到类中。这样您就可以在注入模拟Scanner实例期间注入它们。测试。这是依赖注入的基本步骤,这反过来会导致良好的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。由于我在这个类中没有更多可变状态,因此该类是线程安全的。

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