我有一个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);
}
基于@ McDowell的答案和另一个展示如何测试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
应该很容易。
String
传递给provideInput(String data)
即可。对于我的程序来说,它运行在命令行界面上,每当用户输入一些文本并按下“Enter”键时,该文本就被视为新的输入。请注意,SimpleProgram
使用Java SE Scanner
类来读取用户的输入。 - Antônio MedeirosprovideInput("Input 1\nInput 2\nInput 3");
。 - Antônio Medeiros有几种方法可以解决这个问题。最完整的方法是在运行测试类时传入一个InputStream,这是一个伪造的InputStream,它将模拟数据传递给你的类。如果您需要在代码中经常执行此操作,则可以查看依赖注入框架(例如Google Guice),但简单的方法是:
public class MyClass {
private InputStream systemIn;
public MyClass() {
this(System.in);
}
public MyClass(InputStream in) {
systemIn = in;
}
}
InputData
作为对InputStream
的高级封装,这样你就可以更关注你的类所做的事情,而不是集成方面的问题。 - OscarRyzSystem.setIn(in)
来代替创建变量 systemIn
。这样,你就可以正常调用 System.in
,但是使用的是更新后的版本。 - LaracondaSystem.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());
}
}
TextFromStandardInputStream
的问题,但SystemOutRule
却可以正常工作。 - mike rodentInputStream
,并将其附加到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
设置为包私有,并进行模拟:易如反掌。
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”类中的方法之前。
@Stefan Birkner,谢谢!
Ref:
https://stackoverflow.com/a/66127606/8317677
https://github.com/stefanbirkner/system-lambda/blob/master/pom.xml
https://github.com/stefanbirkner/system-lambda/blob/master/src/test/java/com/github/stefanbirkner/systemlambda/WithTextFromSystemInTest.java
<properties>
<system-lambda.version>1.2.1</system-lambda.version>
</properties>
<dependencies>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId>
<version>${system-lambda.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class SimpleProgram003 {
public static void main(String[] args) {
try{
String c;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
do{
c = in.readLine();
System.out.println(c);
String d = c;
}while(!c.equals("q"));
}catch(Exception e){
System.out.println("catch Exception");
}
}
}
import static com.github.stefanbirkner.systemlambda.SystemLambda.*;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Unit test for simple App. JUnit 4.x.
*/
public class SimpleProgram003Test {
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 setInput(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 1\nq\n";
setInput(testString);
SimpleProgram003.main(new String[0]);
// String a = getOutput();
assertEquals("Hello 1\r\nq\r\n", getOutput());
}
@Test // Multiply inputs
public void testCase2() throws Exception {
withTextFromSystemIn(
"Input1",
"Input2",
"q",
"Input3"
).execute(() -> {
SimpleProgram003.main(new String[0]);
// String a = getOutput();
assertEquals("Input1\r\nInput2\r\nq\r\n", getOutput());
});
}
}
可能是这样的(未经测试):
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);