使用Scanner对用户输入进行单元测试



我必须在一个类中测试一个方法,该方法使用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(使用mock…当……

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

除切换系统外。正如Codebender所提到的,考虑重构,使getInput()成为对您编写的完整getInput(Scanner)方法的一行调用,您可以通过创建自己的Scanner("yourntestninputn")轻松地对其进行测试。还有许多其他的方法来注入你的扫描器依赖,比如让你覆盖一个字段来测试,但是仅仅让一个方法重载是非常简单的,并且在技术上给了你更多的灵活性(例如,让你添加一个从文件中读取输入的特性)。

一般来说,请记住为易于测试而设计,并对高风险部分进行比低风险部分更多的测试。这意味着重构是一个很好的工具,测试getInput(Scanner)可能比测试getInput()重要得多,特别是当您要做的不仅仅是调用nextLine()时。

我强烈建议不要创建模拟的Scanner:模拟不属于自己的类型不仅是不好的做法,而且Scanner代表了一个非常大的API,其中调用顺序很重要。要在Mockito中复制它,意味着要么在Mockito中创建一个大的伪扫描仪实现,要么模拟一个最小的实现,它只测试你所做的调用(如果你的实现改变了,即使你的改变提供了正确的结果,它也会中断)。使用一个真正的扫描器,并保存Mockito练习外部服务调用或情况下,你正在模拟一个小的尚未编写的API你定义。

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

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

基于构造函数的依赖注入的概念非常酷,值得在网上阅读。

相关内容

  • 没有找到相关文章

最新更新