JUnit test for System.out.println()
我需要为一个设计很差的旧应用程序编写JUnit测试,并将大量错误消息写入标准输出。 当
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @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或者不理解请求时它会返回
有没有办法在JUnit中断言控制台输出? 要抓住像这样的案例:
1 2 |
使用ByteArrayOutputStream和System.setXXX很简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); } |
样本测试用例:
1 2 3 4 5 6 7 8 9 10 11 |
我使用此代码来测试命令行选项(断言-version输出版本字符串等)
编辑:
该答案的先前版本在测试后称为
我知道这是一个旧线程,但是有一个很好的库可以做到这一点:
系统规则
来自文档的示例:
1 2 3 4 5 6 7 8 9 10 | 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()); } } |
它还允许您捕获
我不是重定向
在生产中
1 |
在测试中
1 2 3 4 | ByteArrayOutputStream outSpy = new ByteArrayOutputStream(); ConsoleWriter writer = new ConsoleWriter(new PrintStream(outSpy)); writer.printSomething(); assertThat(outSpy.toString(), is("expected output")); |
讨论
这样,通过简单的重构,可以测试被测试的类,而无需使用系统规则间接重定向标准输出或模糊拦截。
您可以通过setOut()(以及
(我会主张,在某个阶段,将应用程序转换为一些日志框架 - 但我怀疑你已经意识到了这一点!)
稍微偏离主题,但是如果有些人(像我这样,当我第一次找到这个帖子时)可能对通过SLF4J捕获日志输出感兴趣,那么commons-testing的JUnit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 的绑定,但我很乐意添加更多内容。
@dfa答案很棒,所以我更进一步,以便测试输出块。
首先,我用方法
TestHelper的来源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 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 ", outContent.toString() ); } }); } |
如果你使用Spring Boot(你提到你正在使用一个旧的应用程序,所以你可能不是,但它可能对其他人有用),那么你可以使用org.springframework.boot.test.rule.OutputCapture以下列方式:
1 2 3 4 5 6 7 8 | @Rule public OutputCapture outputCapture = new OutputCapture(); @Test public void out() { System.out.print("hello"); assertEquals(outputCapture.toString(),"hello"); } |
基于@dfa的答案和另一个显示如何测试System.in的答案,我想分享我的解决方案,为程序提供输入并测试其输出。
作为参考,我使用JUnit 4.12。
假设我们有这个程序简单地将输入复制到输出:
1 2 3 4 5 6 7 8 9 |
要测试它,我们可以使用以下类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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运行
我没有测试
完整的JUnit 5示例测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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"); } } |
您不希望重定向system.out流,因为它重定向到整个JVM。在JVM上运行的任何其他东西都可能搞砸了。有更好的方法来测试输入/输出。查看存根/模拟。
出去
1 2 3 4 5 6 7 8 9 10 11 12 | @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! ", out.toString()); System.setOut(save_out); } |
为了错误
1 2 3 4 5 6 7 8 9 10 11 12 | @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! ", err.toString()); System.setErr(save_err); } |
使用JUnit时,无法使用system.out.println或使用logger api直接打印。但是如果你想检查任何值,那么你就可以使用了
1 | Assert.assertEquals("value", str); |
它将抛出以下断言错误:
1 | java.lang.AssertionError: expected [21.92] but found [value] |
你的值应该是21.92,现在如果你将测试使用这个值,如下所示,你的测试用例将通过。
1 | Assert.assertEquals(21.92, str); |