在Java程序中,可以通过给System.in
和System.out
重新赋值来重定向标准输入和输出。
在单元测试的时候,为了对涉及到标准输入和输出的程序进行测试,常用的做法是将标准输入和输出重定向到文件,这样就可以用程序实现自动化的比对。但是这样做会有文件读取的开销,可能会降低单元测试的运行效率。
所以,更优的做法是将标准输入和输出重定向到字节流,这样就能避免文件读取的开销。
为了实现以上的效果,可以封装一个叫做getOutput
的工具函数,它以字符串的形式传递标准输入内容,并以字符串的形式返回标准输出的内容。在getOutput
内部通过对System.in
和System.out
重新赋值来实现重定向的效果,并在测试完成之后恢复原来的值。
public interface Executable {
void execute();
}
public String getOutput(String input, Executable executable) {
try (
// 字节输入流
ByteArrayInputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
// 字节输出流
ByteArrayOutputStream os = new ByteArrayOutputStream()
) {
// 保存原始的标准输入和输出
InputStream in = System.in;
PrintStream out = System.out;
// 将标准输入和输出重定向到字节流
System.setIn(is);
System.setOut(new PrintStream(os));
// 执行待验证的程序
executable.execute();
// 恢复标准输入和输出
System.setOut(out);
System.setIn(in);
// 返回标准输出的内容
return os.toString();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
可以写以下的测试程序:
public void funcToTest() {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int a = scanner.nextInt();
int b = scanner.nextInt();
System.out.println(a + b);
}
}
String output = getOutput("2 3 10 20 55 100", () -> funcToTest());
System.out.println(output);
控制台输出如下:
5
30
155
然而,由于System.in
和System.out
本质上是全局变量,所以以上程序会有线程安全问题,当有多个线程同时调用getOutput
方法时,输出内容会错乱。
更优的做法是让被测函数不直接依赖于System.in
和System.out
,而是依赖于更抽象的InputStream
和OutputStream
:
public static void funcToTest(InputStream is, OutputStream os) {
Scanner scanner = new Scanner(is);
PrintStream printStream = new PrintStream(os);
while (scanner.hasNext()) {
int a = scanner.nextInt();
int b = scanner.nextInt();
printStream.println(a + b);
}
}
然后改造Executable
接口和getOutput
方法:
public interface Executable {
void execute(InputStream is, OutputStream os);
}
public String getOutput(String input, Executable executable) {
try (
// 字节输入流
ByteArrayInputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
// 字节输出流
ByteArrayOutputStream os = new ByteArrayOutputStream()
) {
// 执行待验证的程序
executable.execute(is, os);
// 返回标准输出的内容
return os.toString();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
测试代码:
String output = getOutput("2 3 10 20 55 100", (is, os) -> funcToTest(is, os));
System.out.println(output);
运行结果与之前相同:
5
30
155