我必须在一个类中测试一个方法,该方法使用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。因为我在这个类中没有更多的可变状态,所以这个类是线程安全的。
基于构造函数的依赖注入的概念非常酷,值得在网上阅读。