我必须在类中测试一个方法,该方法使用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一起尝试(使用模拟......当...然后返回)但不知道该怎么做。
答案 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。由于我在这个类中没有更多可变状态,因此该类是线程安全的。
基于构造函数的依赖注入的概念非常酷,值得在互联网上阅读。它有助于开发出良好的线程安全可测试代码。