我有一个Java命令行程序。 我想创建JUnit测试案例能够模拟System.in
。 因为当我的程序运行时它会进入while循环,并等待来自用户的输入。 如何模拟在JUnit的?
谢谢
我有一个Java命令行程序。 我想创建JUnit测试案例能够模拟System.in
。 因为当我的程序运行时它会进入while循环,并等待来自用户的输入。 如何模拟在JUnit的?
谢谢
这在技术上是可以切换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);
}
根据@麦克道尔的回答和另一种答案,说明如何进行测试的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
。
有几种方法可以解决这个。 最完整的方式是在一个InputStream通过在运行测试中的类,它是通过模拟数据类假的InputStream。 你可以看一下依赖注入框架(如谷歌吉斯)如果你需要做这个有很多在你的代码,但简单的方法是:
public class MyClass {
private InputStream systemIn;
public MyClass() {
this(System.in);
}
public MyClass(InputStream in) {
systemIn = in;
}
}
根据测试,你会调用了一个输入流的构造。 你甚至云作出这样的构造函数包私有,并把测试在同一个包,让其他的代码通常不会考虑使用它。
尝试重构你的代码中使用依赖注入 。 而不是你的使用方法的System.in
直接,有法接受InputStream
作为参数。 然后在你的JUnit测试,你就可以通过测试InputStream
执行到位的System.in
。
您可以通过使用写命令行界面清晰的测试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());
}
}
披露:我是该库的作者。
你可以创建一个自定义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 );
虽然我认为你应该更好地测试你的类应该如何工作,从输入流中读取数据,并没有真正如何从那里读取。
这个问题BufferedReader.readLine()
是,它是它等待用户输入的阻塞方法。 在我看来,你并不特别想以模拟(也就是说你想测试要快)。 但在测试范围内它会不断返回null
测试,这是令人厌烦的过程中高速。
对于一个纯粹的,你可以使getInputLine
下面包私人和嘲笑它:易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”级触发关闭方法。
也许是这样的(未测试):
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);