ChatGPT解决这个技术问题 Extra ChatGPT

System.out.println() 的 JUnit 测试

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

C
Community

使用 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 的原因。


此外,我使用 JUnitMatchers 来测试响应: assertThat(result, containsString("
我更喜欢使用 System.setOut(null) 将流恢复到启动 VM 时的状态
javadocs 没有说明能够将 null 传递给 System.setOut 或 System.setErr。您确定这适用于所有 JRE 吗?
在按照上面的建议设置空错误流后,我在其他测试中遇到了 NullPointerException(在 java.io.writer(Object) 中,由 XML 验证器内部调用)。我建议将原件保存在一个字段中:oldStdErr = System.err 并在 @After 方法中恢复它。
很好的解决方案。只是给任何使用它的人的注释,您可能需要从 outContent 中修剪()空格/换行符。
J
Jorengarenar

我知道这是一个旧线程,但有一个很好的库可以做到这一点: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) 和其他需要测试命令行工具的内容。


这种方法充满了问题,因为标准输出流是程序所有部分使用的共享资源。最好使用依赖注入来消除直接使用标准输出流:stackoverflow.com/a/21216342/545127
R
Raedwald

我不会重定向 System.out,而是重构使用 System.out.println() 的类,方法是将 PrintStream 作为协作者传递,然后在生产中使用 System.out,在测试中使用 Test Spy。即使用依赖注入来消除对标准输出流的直接使用。

生产中

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

讨论

通过这种方式,被测类通过简单的重构变得可测试,而无需间接重定向标准输出或使用系统规则进行模糊拦截。


我在 JDK 的任何地方都找不到这个 ConsoleWriter:它在哪里?
答案中可能应该提到它,但我相信该类是由 user1909402 创建的。
我认为 ConsoleWriter 是测试对象,
你不会在 jdk 中找到一个简单的 consolewrite 类,但实现起来应该很简单
B
Brian Agnew

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

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


这是我想到的事情,但我不敢相信没有标准的 JUnit 方法可以做到这一点。谢谢,大脑。但实际工作的功劳归于 dfa。
这种方法充满了问题,因为标准输出流是程序所有部分使用的共享资源。最好使用依赖注入来消除直接使用标准输出流:stackoverflow.com/a/21216342/545127
是的。我会支持这一点,甚至可能会质疑日志记录断言(最好断言对日志记录组件或类似组件的调用)
M
Marc Carré

有点跑题了,但如果有些人(比如我,当我第一次发现这个线程时)可能对通过 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));
    }
}

免责声明:

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

目前只有 log4j、log4j2 和 logback 的绑定可用,但我很乐意添加更多。


非常感谢您创建这个库!我一直在寻找这样的东西这么久!它非常非常有用,因为有时您根本无法将代码简化到足以轻松测试的程度,但是通过日志消息您可以创造奇迹!
这看起来很有希望......但是即使我只是复制您的 ATMTest 程序并在 Gradle 下将其作为测试运行,我也遇到了异常......我在您的 Github 页面上提出了一个问题......
D
Disper

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

我对您的回答投了赞成票,因为我使用了 Spring Boot,它让我走上了正确的道路。谢谢!但是,需要初始化 outputCapture。 (public OutputCapture outputCapture = new OutputCapture();) 见 docs.spring.io/spring-boot/docs/current/reference/html/…
m
mguymon

@dfa 的答案很棒,所以我更进一步,使测试输出块成为可能。

首先,我使用接受无用类 CaptureTest 的方法 captureOutput 创建了 TestHelper。 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() );
            }
        });
}

A
Antônio Medeiros

基于 @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注解提供输入(String data),从testCase1()调用getOutput(),从testCase1()调用restoreSystemInputOutput(),因为@After注解

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


J
Jens Piegsa

测试 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");
    }
}

S
Sam Jacobs

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


S
Shimon Doodkin

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

对于这种设置和拆卸逻辑,我将使用 @Rule,而不是在您的测试中内联。值得注意的是,如果您的断言失败,第二个 System.setOut/Err 调用将不会到达。
s
sumeet

如果该函数正在打印到 System.out,您可以通过使用 System.setOut 方法将 System.out 更改为您提供的 PrintStream 来捕获该输出。如果创建连接到 ByteArrayOutputStream 的 PrintStream,则可以将输出捕获为字符串。

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

H
Hakan54

虽然这个问题很老而且已经有了很好的答案,但我想提供一个替代方案。我喜欢 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();
    }
}

A
Aftab

在使用 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);