问题描述
我必须在一个使用 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... when ... thenReturn),但不知道该怎么做。
1楼
您可以使用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
读取。
所以它不再从控制台读取,而是从提供的输入流读取。
2楼
您可以使用库的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楼
除了,正如 Codebender 还提到的,考虑重构,以便getInput()
成为对您编写的彻底getInput(Scanner)
方法的单行调用,您可以通过创建自己的Scanner("your\\ntest\\ninput\\n")
。
还有许多其他方法可以注入您的扫描仪依赖项,例如制作一个您覆盖测试的字段,但是仅制作一个方法重载非常简单,并且在技术上为您提供了更大的灵活性(让您添加一个功能以从文件中读取输入,例如)。
一般来说,请记住设计易于测试,并且比低风险部分更重地测试高风险部分。
这意味着重构是一个很好的工具,并且测试getInput(Scanner)
可能比测试getInput()
重要得多,尤其是当您所做的不仅仅是调用nextLine()
。
我强烈建议不要创建模拟 Scanner:模拟您不拥有的类型不仅是不好的做法,而且 Scanner 代表了一个非常大的相互关联方法的 API,其中调用顺序很重要。 在 Mockito 中复制它意味着你要么在 Mockito 中创建一个大的假 Scanner 实现,要么模拟一个最小的实现,它只测试你所做的调用(如果你的实现发生变化,即使你的更改提供了正确的结果,也会中断)。 使用真正的 Scanner 并保存 Mockito 练习以用于外部服务调用或模拟您定义的小型但未编写的 API 的情况。
4楼
首先,我假设您测试的目的是验证用户输入是从扫描仪获取的,并且返回的值是扫描仪中输入的值。
您嘲笑不起作用的原因是因为您每次都在getInput()
方法中创建实际的扫描仪对象。
因此,无论你做什么,你的 mockito 实例都不会被调用。
因此,使此类可测试的正确方法是识别该类的所有外部依赖项(在本例中为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。 由于我在这个类中没有更多的可变状态,这个类是线程安全的。
基于构造函数的依赖注入的概念非常酷,值得在互联网上阅读。 它有助于开发良好的线程安全可测试代码。