我有一个Java命令行程序。我想创建JUnit测试用例以便能够模拟System.in
。因为当我的程序运行时,它将进入while循环并等待来自用户的输入。我如何在JUnit中模拟它?
由于
答案 0 :(得分:60)
技术上可以切换System.in
,但一般情况下,不直接在代码中调用它会更强大,但添加一个间接层,以便从您的一个点控制输入源应用。具体如何做到这一点是一个实现细节 - 依赖注入的建议很好,但你不一定需要引入第三方框架;例如,您可以从调用代码传递I / O上下文。
如何切换System.in
:
String data = "Hello, World!\r\n";
InputStream stdin = System.in;
try {
System.setIn(new ByteArrayInputStream(data.getBytes()));
Scanner scanner = new Scanner(System.in);
System.out.println(scanner.nextLine());
} finally {
System.setIn(stdin);
}
答案 1 :(得分:10)
基于@McDowell's answer和another answer that shows how to test System.out,我想分享我的解决方案,为程序提供输入并测试其输出。
作为参考,我使用JUnit 4.12。
假设我们有这个程序只是简单地将输入复制到输出:
import java.util.Scanner;
public class SimpleProgram {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print(scanner.next());
scanner.close();
}
}
要测试它,我们可以使用以下类:
import static org.junit.Assert.*;
import java.io.*;
import org.junit.*;
public class SimpleProgramTest {
private final InputStream systemIn = System.in;
private final PrintStream systemOut = System.out;
private ByteArrayInputStream testIn;
private ByteArrayOutputStream testOut;
@Before
public void setUpOutput() {
testOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(testOut));
}
private void provideInput(String data) {
testIn = new ByteArrayInputStream(data.getBytes());
System.setIn(testIn);
}
private String getOutput() {
return testOut.toString();
}
@After
public void restoreSystemInputOutput() {
System.setIn(systemIn);
System.setOut(systemOut);
}
@Test
public void testCase1() {
final String testString = "Hello!";
provideInput(testString);
SimpleProgram.main(new String[0]);
assertEquals(testString, getOutput());
}
}
我不会解释太多,因为我相信代码是可读的,我引用了我的来源。
当JUnit运行testCase1()
时,它将按照它们出现的顺序调用辅助方法:
setUpOutput()
,因为@Before
注释provideInput(String data)
,来自testCase1()
getOutput()
,来自testCase1()
restoreSystemInputOutput()
,因为@After
注释我没有测试System.err
,因为我不需要它,但它应该很容易实现,类似于测试System.out
。
答案 2 :(得分:8)
有几种方法可以解决这个问题。最完整的方法是在运行被测试类时传入一个InputStream,这是一个伪造的InputStream,它将模拟数据传递给您的类。如果您需要在代码中执行此操作,可以查看依赖注入框架(例如Google Guice),但简单的方法是:
public class MyClass {
private InputStream systemIn;
public MyClass() {
this(System.in);
}
public MyClass(InputStream in) {
systemIn = in;
}
}
在测试中,您将调用获取输入流的构造函数。您甚至将该构造函数包设为私有并将测试放在同一个包中,以便其他代码通常不会考虑使用它。
答案 3 :(得分:5)
尝试重构您的代码以使用dependency injection。不要让您的方法直接使用System.in
,而是让方法接受InputStream
作为参数。然后在junit测试中,您将能够通过测试InputStream
实施代替System.in
。
答案 4 :(得分:4)
您可以使用System Rules库的TextFromStandardInputStream
规则为命令行界面编写明确的测试。
public void MyTest {
@Rule
public final TextFromStandardInputStream systemInMock
= emptyStandardInputStream();
@Test
public void readTextFromStandardInputStream() {
systemInMock.provideLines("foo");
Scanner scanner = new Scanner(System.in);
assertEquals("foo", scanner.nextLine());
}
}
完全披露:我是该图书馆的作者。
答案 5 :(得分:1)
您可以创建自定义InputStream
并将其附加到 System
类
class FakeInputStream extends InputStream {
public int read() {
return -1;
}
}
然后将其与Scanner
<击> System.in = new FakeInputStream();
击>
在:
InputStream in = System.in;
...
Scanner scanner = new Scanner( in );
后:
InputStream in = new FakeInputStream();
...
Scanner scanner = new Scanner( in );
虽然我认为你应该更好地测试你的类应该如何处理从输入流中读取的数据,而不是从它那里读取的数据。
答案 6 :(得分:1)
BufferedReader.readLine()
的问题在于它是一种等待用户输入的阻塞方法。在我看来,你并不特别想模拟它(即你想要快速测试)。但是在测试环境中,它会在测试过程中不断高速返回null
,这很麻烦。
对于一个纯粹主义者,你可以将getInputLine
下面的包私有,然后嘲笑它:easy-peezy。
String getInputLine() throws Exception {
return br.readLine();
}
...你必须确保你有办法停止(通常)用户与应用程序交互的循环。你还必须应对这样一个事实,即你的“输入线”总是相同的,直到你以某种方式改变你的模拟的doReturn
:几乎不是典型的用户输入。
对于希望让自己轻松生活(并制作可读测试)的非纯粹主义者,您可以将以下所有内容放在您的应用代码中:
private Deque<String> inputLinesDeque;
void setInputLines(List<String> inputLines) {
inputLinesDeque = new ArrayDeque<String>(inputLines);
}
private String getInputLine() throws Exception {
if (inputLinesDeque == null) {
// ... i.e. normal case, during app run: this is then a blocking method
return br.readLine();
}
String nextLine = null;
try {
nextLine = inputLinesDeque.pop();
} catch (NoSuchElementException e) {
// when the Deque runs dry the line returned is a "poison pill",
// signalling to the caller method that the input is finished
return "q";
}
return nextLine;
}
...在你的测试中你可能会这样:
consoleHandler.setInputLines( Arrays.asList( new String[]{ "first input line", "second input line" }));
在触发此“ConsoleHandler”类中需要输入行的方法之前。
答案 7 :(得分:0)
也许是这样(未经测试):
InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));
in.write("text".getBytes("utf-8"));
System.setIn( save_in );
更多部分:
//PrintStream save_out=System.out;final ByteArrayOutputStream out = new ByteArrayOutputStream();System.setOut(new PrintStream(out));
InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));
//start something that reads stdin probably in a new thread
// Thread thread=new Thread(new Runnable() {
// @Override
// public void run() {
// CoursesApiApp.main(new String[]{});
// }
// });
// thread.start();
//maybe wait or read the output
// for(int limit=0; limit<60 && not_ready ; limit++)
// {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
in.write("text".getBytes("utf-8"));
System.setIn( save_in );
//System.setOut(save_out);