我需要为一个设计很差的旧应用程序编写JUnit测试,并将大量错误消息写入标准输出。当getResponse(String request)
方法行为正确时,它返回XML响应:
@BeforeClass
public static void setUpClass() throws Exception {
Properties queries = loadPropertiesFile("requests.properties");
Properties responses = loadPropertiesFile("responses.properties");
instance = new ResponseGenerator(queries, responses);
}
@Test
public void testGetResponse() {
String request = "<some>request</some>";
String expResult = "<some>response</some>";
String result = instance.getResponse(request);
assertEquals(expResult, result);
}
但是当它得到格式错误的XML或者不理解请求时它返回null
并将一些内容写入标准输出。
有没有办法在JUnit中断言控制台输出?抓住像:
这样的案例System.out.println("match found: " + strExpr);
System.out.println("xml not well formed: " + e.getMessage());
答案 0 :(得分:515)
使用ByteArrayOutputStream和System.setXXX很简单:
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;
@Before
public void setUpStreams() {
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));
}
@After
public void restoreStreams() {
System.setOut(originalOut);
System.setErr(originalErr);
}
示例测试用例:
@Test
public void out() {
System.out.print("hello");
assertEquals("hello", outContent.toString());
}
@Test
public void err() {
System.err.print("hello again");
assertEquals("hello again", errContent.toString());
}
我使用此代码测试命令行选项(断言-version输出版本字符串等)
修改强>
该答案的先前版本在测试后称为System.setOut(null)
;这是NullPointerExceptions评论者引用的原因。
答案 1 :(得分:93)
我知道这是一个旧线程,但有一个很好的库可以做到这一点:
文档示例:
public void MyTest {
@Rule
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();
@Test
public void overrideProperty() {
System.out.print("hello world");
assertEquals("hello world", systemOutRule.getLog());
}
}
它还允许您捕获System.exit(-1)
以及命令行工具需要测试的其他内容。
答案 2 :(得分:22)
您可以通过setOut()(以及in
和err
)设置System.out打印流。您可以将其重定向到记录到字符串的打印流,然后检查它吗?这似乎是最简单的机制。
(我会主张,在某个阶段,将应用程序转换为一些日志框架 - 但我怀疑你已经意识到了这一点!)
答案 3 :(得分:22)
我不会重定向System.out
,而是通过传递System.out.println()
作为协作者,然后在生产中使用PrintStream
和来重构使用System.out
的类。在测试中测试间谍。也就是说,使用依赖注入来消除标准输出流的直接使用。
生产中
ConsoleWriter writer = new ConsoleWriter(System.out));
在测试中
ByteArrayOutputStream outSpy = new ByteArrayOutputStream();
ConsoleWriter writer = new ConsoleWriter(new PrintStream(outSpy));
writer.printSomething();
assertThat(outSpy.toString(), is("expected output"));
<强>讨论强>
这样,通过简单的重构,可以测试被测试的类,而无需使用系统规则间接重定向标准输出或模糊拦截。
答案 4 :(得分:12)
稍微偏离主题,但是如果某些人(像我这样,当我第一次找到这个帖子时)可能对通过SLF4J捕获日志输出感兴趣,commons-testing的JUnit @Rule
可能会有所帮助:< / p>
public class FooTest {
@Rule
public final ExpectedLogs logs = new ExpectedLogs() {{
captureFor(Foo.class, LogLevel.WARN);
}};
@Test
public void barShouldLogWarning() {
assertThat(logs.isEmpty(), is(true)); // Nothing captured yet.
// Logic using the class you are capturing logs for:
Foo foo = new Foo();
assertThat(foo.bar(), is(not(nullValue())));
// Assert content of the captured logs:
assertThat(logs.isEmpty(), is(false));
assertThat(logs.contains("Your warning message here"), is(true));
}
}
免责声明:
log4j
,log4j2
和logback
的绑定,但我很乐意添加更多内容。答案 5 :(得分:9)
@dfa答案很棒,所以我更进了一步,以便测试输出块。
首先,我使用方法TestHelper
创建captureOutput
,接受烦人的班级CaptureTest
。 captureOutput方法执行设置和拆除输出流的工作。当调用CaptureOutput
test
方法的实现时,它可以访问测试块的输出生成。
TestHelper的来源:
public class TestHelper {
public static void captureOutput( CaptureTest test ) throws Exception {
ByteArrayOutputStream outContent = new ByteArrayOutputStream();
ByteArrayOutputStream errContent = new ByteArrayOutputStream();
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));
test.test( outContent, errContent );
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
System.setErr(new PrintStream(new FileOutputStream(FileDescriptor.out)));
}
}
abstract class CaptureTest {
public abstract void test( ByteArrayOutputStream outContent, ByteArrayOutputStream errContent ) throws Exception;
}
请注意,TestHelper和CaptureTest在同一文件中定义。
然后在测试中,您可以导入静态captureOutput。以下是使用JUnit的示例:
// imports for junit
import static package.to.TestHelper.*;
public class SimpleTest {
@Test
public void testOutput() throws Exception {
captureOutput( new CaptureTest() {
@Override
public void test(ByteArrayOutputStream outContent, ByteArrayOutputStream errContent) throws Exception {
// code that writes to System.out
assertEquals( "the expected output\n", outContent.toString() );
}
});
}
答案 6 :(得分:6)
如果你使用Spring Boot(你提到你正在使用一个旧的应用程序,所以你可能不是,但它可能对其他人有用),那么你可以使用 org.springframework.boot .test.rule.OutputCapture 以下列方式:
@Rule
public OutputCapture outputCapture = new OutputCapture();
@Test
public void out() {
System.out.print("hello");
assertEquals(outputCapture.toString(), "hello");
}
答案 7 :(得分:3)
基于@dfa's answer和another answer that shows how to test System.in,我想分享我的解决方案,为程序提供输入并测试其输出。
作为参考,我使用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
。
答案 8 :(得分:1)
您不希望重定向system.out流,因为它重定向到整个JVM。在JVM上运行的任何其他东西都可能搞砸了。有更好的方法来测试输入/输出。查看存根/模拟。
答案 9 :(得分:1)
测试System.out
的完整JUnit 5示例(替换when部分):
package learning;
import static org.assertj.core.api.BDDAssertions.then;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class SystemOutLT {
private PrintStream originalSystemOut;
private ByteArrayOutputStream systemOutContent;
@BeforeEach
void redirectSystemOutStream() {
originalSystemOut = System.out;
// given
systemOutContent = new ByteArrayOutputStream();
System.setOut(new PrintStream(systemOutContent));
}
@AfterEach
void restoreSystemOutStream() {
System.setOut(originalSystemOut);
}
@Test
void shouldPrintToSystemOut() {
// when
System.out.println("example");
then(systemOutContent.toString()).containsIgnoringCase("example");
}
}
答案 10 :(得分:0)
使用 jUnit 时,无法使用 system.out.println 或使用 logger api 直接打印。但是如果你想检查任何值,那么你只需使用
Assert.assertEquals("value", str);
它将抛出以下断言错误:
java.lang.AssertionError: expected [21.92] but found [value]
您的值应为21.92,现在如果您将使用此值进行测试,则测试用例将通过。
Assert.assertEquals(21.92, str);
答案 11 :(得分:0)
for out
@Test
void it_prints_out() {
PrintStream save_out=System.out;final ByteArrayOutputStream out = new ByteArrayOutputStream();System.setOut(new PrintStream(out));
System.out.println("Hello World!");
assertEquals("Hello World!\r\n", out.toString());
System.setOut(save_out);
}
for err
@Test
void it_prints_err() {
PrintStream save_err=System.err;final ByteArrayOutputStream err= new ByteArrayOutputStream();System.setErr(new PrintStream(err));
System.err.println("Hello World!");
assertEquals("Hello World!\r\n", err.toString());
System.setErr(save_err);
}
答案 12 :(得分:0)
如果函数正在打印到System.out,则可以通过使用System.setOut方法将System.out更改为转到您提供的PrintStream来捕获该输出。如果创建连接到ByteArrayOutputStream的PrintStream,则可以将输出捕获为String。
// Create a stream to hold the output
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
// IMPORTANT: Save the old System.out!
PrintStream old = System.out;
// Tell Java to use your special stream
System.setOut(ps);
// Print some output: goes to your special stream
System.out.println("Foofoofoo!");
// Put things back
System.out.flush();
System.setOut(old);
// Show what happened
System.out.println("Here: " + baos.toString());
答案 13 :(得分:0)
虽然这个问题很老而且已经有很好的答案,但我想提供一个替代方案。我喜欢 dfa
的答案,但是我希望在不复制配置的情况下在不同的项目中具有可重用的东西,因此我从中创建了一个库并希望回馈社区。它名为 Console Captor,您可以使用以下代码段添加它:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>consolecaptor</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
示例类
public class FooService {
public void sayHello() {
System.out.println("Keyboard not responding. Press any key to continue...");
System.err.println("Congratulations, you are pregnant!");
}
}
单元测试
import static org.assertj.core.api.Assertions.assertThat;
import nl.altindag.console.ConsoleCaptor;
import org.junit.jupiter.api.Test;
public class FooServiceTest {
@Test
public void captureStandardAndErrorOutput() {
ConsoleCaptor consoleCaptor = new ConsoleCaptor();
FooService fooService = new FooService();
fooService.sayHello();
assertThat(consoleCaptor.getStandardOutput()).contains("Keyboard not responding. Press any key to continue...");
assertThat(consoleCaptor.getErrorOutput()).contains("Congratulations, you are pregnant!");
consoleCaptor.close();
}
}