在java中测试标准输入和输出,而不需要预期将要编写的用户输入



我有一个典型的java类,它从控制台读取用户输入,处理它,并相应地回答。例如:

  • 如果用户键入";你好">
  • 程序回答";你好,世界">

等。

public class ConsoleTalker
{
private Scanner scanner;

public boolean continueExecution;

public ConsoleTalker()
{
this.scanner = new Scanner(System.in);
}

public void Start()
{
this.continueExecution = true;

while(continueExecution)
{
String userInput = this.ReadUserInput();

this.ExecuteAction(userInput);
}
}

private String ReadUserInput()
{
return scanner.nextLine();
}

private void println(String text)
{
System.out.println(text);
}
private void ExecuteAction(String userInput)
{
// ...
}

只有.Start()方法是公共的。所以你启动它,程序会一直进行到:

  • 用户键入";退出">
  • 程序回答";再见&";,设置continueExecuting = false,循环结束

我想测试这个类。我遵循了本网站中的建议:https://www.danvega.dev/blog/2020/12/16/testing-standard-in-out-java/

然而,这带来了一个问题,那就是我需要事先编写所有的userInput:

@Test
public void StartListening_WriteHello_AnswersHelloWorld() {
// Arrange
//here you see I need to anticipate I will write a hello and an exit during the conversation
String userInput = "hello" + System.lineSeparator() + "exit";
String expectedAnswer1 = "Hello world!";
String expectedAnswer2 = "Goodbye!";


// Redirect standard in
ByteArrayInputStream bais = new ByteArrayInputStream(userInput.getBytes());
System.setIn(bais);

// Redirect standard out
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(baos);
System.setOut(printStream);

// Act
var consoleTalker = new ConsoleTalker();
consoleTalker.Start();

// Assert
String[] lines = baos.toString().split(System.lineSeparator());
String actualAnswer1 = lines[lines.length-2];
String actualAnswer2 = lines[lines.length-1];

Assertions.assertEquals(expectedAnswer1, actualAnswer1);
Assertions.assertEquals(expectedAnswer2, actualAnswer2);
Assertions.assertFalse(consoleTalker.continueExecution);
}

如何在测试期间插入输入和输出,来回?其中,我读取输出并根据该输出编写下一个模拟用户输入,或者根据以前的输出计算下一个预期输出

ConsoleTalker的一些输出是不可预测的(例如,如果它包含某种随机答案或id(。如果我需要提前编写测试过程中使用的所有userInput,有些场景是不可测试的

我可以修改ConsoleTalker,使其更易于测试,但为了答案,请尽量避免:

  • 将私有方法转换为公共方法
  • 任何我预期程序输出并可以预先编写所有用户输入的变通方法。我真的需要在测试中反复输入和输出

根据实际测试的内容以及是否可以修改测试中的类,有几种方法。根据这个问题,我假设您不想或不能更改ConsoleTalker。我还假设在单独的过程中运行测试中的类不是一种选择,因为它应该在构建过程中进行测试。

留给我的是PipedInputStream/PipedOutputStream,即为每个方向(读/写(创建一对,重定向System.out和System.in,并在单独的线程中运行ConsoleTalker,例如

public class ConsoleTalkerTest {
private static PrintStream sOut;
public static void main(String[] args) throws Exception {
sOut = System.out;
InputStream sIn = System.in;
try (PipedOutputStream srcOut = new PipedOutputStream();
PipedInputStream srcIn = new PipedInputStream();
PipedInputStream pis = new PipedInputStream(srcOut);
PipedOutputStream pos = new PipedOutputStream(srcIn);
BufferedReader reader = new BufferedReader(new InputStreamReader(pis));
PrintStream writer = new PrintStream(pos, true)) {

System.setOut(new PrintStream(srcOut, true));
System.setIn(srcIn);
Thread th = new Thread(() -> {
ConsoleTalker talker = new ConsoleTalker();
talker.Start();
});
th.start();
writer.println("Test");
String response = reader.readLine();
showResults("Test", response);

writer.println("exit");
response = reader.readLine();
showResults("ABCDEF", response);
} finally {
System.setIn(sIn);
System.setOut(sOut);
}
}
static void showResults(String expected, String actual) {
if (Objects.equals(expected, actual)) {
sOut.println("Received expected answer " + expected);
} else {
sOut.println("Expected " + expected + " but got " + actual);
}
}
}

相关内容

最新更新