Java JUnit:如何模拟 System.in 测试?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1647907/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
JUnit: How to simulate System.in testing?
提问by Noppanit
I have a Java command-line program. I would like to create JUnit test case to be able to simulate System.in
. Because when my program runs it will get into the while loop and waits for input from users. How do I simulate that in JUnit?
我有一个 Java 命令行程序。我想创建 JUnit 测试用例以便能够模拟System.in
. 因为当我的程序运行时,它会进入 while 循环并等待用户的输入。我如何在 JUnit 中模拟它?
Thanks
谢谢
采纳答案by McDowell
It is technically possible to switch System.in
, but in general, it would be more robust not to call it directly in your code, but add a layer of indirection so the input source is controlled from one point in your application. Exactly how you do that is an implementation detail - the suggestions of dependency injection are fine, but you don't necessarily need to introduce 3rd party frameworks; you could pass round an I/O context from the calling code, for example.
从技术上讲, switch 是可能的System.in
,但一般来说,不直接在代码中调用它会更健壮,而是添加一个间接层,以便从应用程序中的一个点控制输入源。具体如何做到这一点是一个实现细节 - 依赖注入的建议很好,但您不一定需要引入 3rd 方框架;例如,您可以从调用代码传递 I/O 上下文。
How to switch System.in
:
如何切换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);
}
回答by OscarRyz
You could create a custom InputStream
and attach it to the System
class
您可以创建一个自定义InputStream
并将其附加到System
类
class FakeInputStream extends InputStream {
public int read() {
return -1;
}
}
And then use it with your Scanner
然后和你的一起使用 Scanner
System.in = new FakeInputStream();
System.in = new FakeInputStream();
Before:
前:
InputStream in = System.in;
...
Scanner scanner = new Scanner( in );
After:
后:
InputStream in = new FakeInputStream();
...
Scanner scanner = new Scanner( in );
Although I think you should better to test how your class should work with the data read from the input stream and not really how it reads from there.
虽然我认为你应该更好地测试你的类应该如何处理从输入流读取的数据,而不是它如何从那里读取。
回答by Asaph
Try to refactor your code to use dependency injection. Instead of having your a method that uses System.in
directly, have the method accept an InputStream
as an argument. Then in your junit test, you'll be able to pass a test InputStream
implementation in place of System.in
.
尝试重构您的代码以使用依赖注入。不要让你的方法System.in
直接使用,而是让方法接受 anInputStream
作为参数。然后在您的 junit 测试中,您将能够通过测试InputStream
实现来代替System.in
.
回答by Yishai
There are a few ways to approach this. The most complete way is to pass in an InputStream while running the class under test which is a fake InputStream which passes simulated data to your class. You can look at a dependency injection framework (such as Google Guice) if you need to do this a lot in your code, but the simple way is:
有几种方法可以解决这个问题。最完整的方法是在运行被测类时传入一个 InputStream,它是一个假的 InputStream,它将模拟数据传递给您的类。如果你需要在代码中做很多事情,你可以看看一个依赖注入框架(比如 Google Guice),但简单的方法是:
public class MyClass {
private InputStream systemIn;
public MyClass() {
this(System.in);
}
public MyClass(InputStream in) {
systemIn = in;
}
}
Under test you would call the constructor that takes the input stream. You cloud even make that constructor package private and put the test in the same package, so that other code would not generally consider using it.
在测试中,您将调用接受输入流的构造函数。您甚至可以将该构造函数包设为私有并将测试放在同一个包中,这样其他代码通常不会考虑使用它。
回答by Stefan Birkner
You can write a clear test for the command line interface by using the TextFromStandardInputStream
rule of the System Ruleslibrary.
您可以使用系统规则库的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());
}
}
Full disclosure: I'm the author of that library.
完全披露:我是那个图书馆的作者。
回答by mike rodent
The problem with BufferedReader.readLine()
is that it is a blocking method which waits for user input. It seems to me that you don't particularly want to simulate that (i.e. you want tests to be fast). But in a testing context it continually returns null
at high speed during testing, which is irksome.
问题BufferedReader.readLine()
在于它是一种等待用户输入的阻塞方法。在我看来,您并不特别想模拟它(即您希望测试快速)。但是在测试环境中,它null
在测试过程中不断地高速返回,这很烦人。
For a purist you can make the getInputLine
below package-private, and mock it: easy-peezy.
对于纯粹主义者,您可以将getInputLine
以下包设为私有,并模拟它:easy-peezy。
String getInputLine() throws Exception {
return br.readLine();
}
... you'd have to make sure that you had a way of stopping (typically) a loop of user interaction with the app. You'd also have to cope with the fact that your "input lines" would always be the same until you somehow changed the doReturn
of your mock: hardly typical of user input.
...你必须确保你有办法停止(通常)用户与应用程序交互的循环。您还必须应对这样一个事实,即您的“输入行”将始终相同,直到您以某种方式更改了doReturn
模拟的内容:这几乎不是用户输入的典型特征。
For a non-purist who wishes to make life easy for themselves (and produce readable tests) you could put all this stuff below in your app code:
对于希望让自己的生活更轻松(并生成可读测试)的非纯粹主义者,您可以将所有这些内容放在您的应用程序代码中:
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;
}
... in your test you might then go like this:
...在您的测试中,您可能会像这样:
consoleHandler.setInputLines( Arrays.asList( new String[]{ "first input line", "second input line" }));
before triggering off the method in this "ConsoleHandler" class which needs input lines.
在触发这个需要输入行的“ConsoleHandler”类中的方法之前。
回答by Shimon Doodkin
maybe like this (not tested):
也许像这样(未测试):
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 );
more parts:
更多部分:
//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);
回答by Antonio Vinicius Menezes Medei
Based on @McDowell's answerand another answer that shows how to test System.out, I would like to share my solution to give an input to a program and test its output.
基于@McDowell 的回答和另一个显示如何测试 System.out的答案,我想分享我的解决方案,为程序提供输入并测试其输出。
As a reference, I use JUnit 4.12.
作为参考,我使用 JUnit 4.12。
Let's say we have this program that simply replicates input to output:
假设我们有一个简单地将输入复制到输出的程序:
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();
}
}
To test it, we can use the following class:
为了测试它,我们可以使用以下类:
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());
}
}
I won't explain much, because I believe the code is readable and I cited my sources.
我不会解释太多,因为我相信代码是可读的并且我引用了我的来源。
When JUnit runs testCase1()
, it is going to call the helper methods in the order they appear:
当 JUnit 运行时testCase1()
,它将按照它们出现的顺序调用辅助方法:
setUpOutput()
, because of the@Before
annotationprovideInput(String data)
, called fromtestCase1()
getOutput()
, called fromtestCase1()
restoreSystemInputOutput()
, because of the@After
annotation
setUpOutput()
,因为@Before
注释provideInput(String data)
,从testCase1()
getOutput()
,从testCase1()
restoreSystemInputOutput()
,因为@After
注释
I didn't test System.err
because I didn't need it, but it should be easy to implement, similar to testing System.out
.
我没有测试,System.err
因为我不需要它,但它应该很容易实现,类似于 testing System.out
。