首页 文章

System.out.println()的JUnit测试

提问于
浏览
297

我需要为一个设计很差的旧应用程序编写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中断言控制台输出?要 grab 像这样的案例:

System.out.println("match found: " + strExpr);
System.out.println("xml not well formed: " + e.getMessage());

12 回答

  • 7

    使用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输出版本字符串等)

    Edit: 此答案的先前版本在测试后称为 System.setOut(null) ;这是NullPointerExceptions评论者引用的原因 .

  • 21

    我知道这是一个旧线程,但是有一个很好的库可以做到这一点:

    System Rules

    来自文档的示例:

    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) 以及命令行工具需要测试的其他内容 .

  • 10

    您可以通过setOut()(以及 inerr )设置System.out打印流 . 你能将它重定向到记录到字符串的打印流,然后检查它吗?这似乎是最简单的机制 .

    (我会在某个阶段提倡将应用程序转换为一些日志框架 - 但我怀疑你已经意识到了这一点!)

  • 1

    我不会重定向 System.out ,而是通过传递 PrintStream 作为协作者然后在 生产环境 中使用 System.out 并在测试中使用Test Spy来重构使用 System.out.println() 的类 . 也就是说,使用依赖注入来消除标准输出流的直接使用 .

    In Production

    ConsoleWriter writer = new ConsoleWriter(System.out));
    

    In the Test

    ByteArrayOutputStream outSpy = new ByteArrayOutputStream();
    ConsoleWriter writer = new ConsoleWriter(new PrintStream(outSpy));
    writer.printSomething();
    assertThat(outSpy.toString(), is("expected output"));
    

    Discussion

    这样,通过简单的重构,可以测试被测试的类,而无需使用系统规则间接重定向标准输出或模糊拦截 .

  • 0

    稍微偏离主题,但是如果有些人(像我这样,当我第一次找到这个帖子时)可能对通过SLF4J捕获日志输出感兴趣,commons-testing的JUnit @Rule 可能会有所帮助:

    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));
        }
    }
    

    Disclaimer

    • 我开发了这个库,因为我找不到适合自己需要的解决方案 .

    • 目前只有 log4jlog4j2logback 的绑定可用,但我很乐意添加更多内容 .

  • 1

    @dfa答案很棒,所以我更进一步,以便测试输出块 .

    首先,我使用方法 captureOutput 创建了 TestHelper ,它接受了烦人的类 CaptureTest . captureOutput方法执行设置和拆除输出流的工作 . 当调用 CaptureOutputtest 方法的实现时,它可以访问测试块的输出生成 .

    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() );
                }
            });
    }
    
  • 0

    如果您使用的是Spring Boot(您提到了're working with an old application, so you probably aren'但它可能对其他人有用),那么您可以按以下方式使用 org.springframework.boot.test.rule.OutputCapture

    @Rule
    public OutputCapture outputCapture = new OutputCapture();
    
    @Test
    public void out() {
        System.out.print("hello");
        assertEquals(outputCapture.toString(), "hello");
    }
    
  • 0

    您不希望重定向system.out流,因为它重定向到整个JVM . 在JVM上运行的任何其他东西都可能搞砸了 . 有更好的方法来测试输入/输出 . 查看存根/模拟 .

  • 17

    基于@dfa's answeranother 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 .

  • 87

    使用_593122时,无法使用 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);
    
  • 482

    出去

    @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);
    }
    

    为了错误

    @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);
    }
    
  • 6

    完整的JUnit 5示例测试 System.out (替换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");
        }
    }
    

相关问题