我需要为一个设计很差的旧应用程序编写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());

当前回答

基于@dfa的回答和另一个回答,说明如何测试系统。在这里,我想分享我的解决方案,给程序输入并测试其输出。

作为参考,我使用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()时,它将按照helper方法出现的顺序调用它们:

setUpOutput(),因为@Before注释 provideInput(字符串数据),从testCase1()调用 getOutput(),从testCase1()调用 restoreSystemInputOutput(),因为有@After注释

我没有测试系统。错误,因为我不需要它,但它应该很容易实现,类似于测试System.out。

其他回答

完整的JUnit 5示例来测试系统。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");
    }
}

虽然这个问题已经很老了,而且已经有了很好的答案,但我想提供一个替代方案。我喜欢dfa的答案,但是我想在不同的项目中有一些可重用的东西,而不复制配置,所以我用它创建了一个库,并想贡献给社区。它被称为控制台捕捉器,你可以添加下面的代码片段:

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

你不想重定向系统。out流,因为它为整个JVM重定向。JVM上运行的任何其他东西都可能被搞砸。有更好的方法来测试输入/输出。查看存根/模拟。

@dfa的答案是伟大的,所以我采取了进一步的步骤,使测试输出块成为可能。

首先,我用一个方法captureOutput创建了TestHelper,该方法接受匿名类CaptureTest。captureOutput方法执行设置和分解输出流的工作。当CaptureOutput的测试方法的实现被调用时,它可以访问为测试块生成的输出。

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

如果您正在使用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");
}