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());
333784 次浏览

您可以设置“系统”。输出打印流通过一套()(和inerr)。你能把它重定向到一个打印流,记录到一个字符串,然后检查它吗?这似乎是最简单的机制。

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

使用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注释引用。

的原因

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

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

我知道这是一个旧线程,但有一个很好的库可以做到这一点:系统规则
文档示例:

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)和其他命令行工具需要测试的东西。

我不重定向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"));

讨论

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

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

免责声明:

  • 我开发了这个库,因为我找不到任何适合我自己需求的解决方案。
  • 目前只有log4jlog4j2logback的绑定可用,但我很乐意添加更多。

不能直接使用system.out.println或在使用JUnit时使用日志api进行打印。但如果你想检查任何值,那么你可以简单地使用

Assert.assertEquals("value", str);

它将抛出以下断言错误:

java.lang.AssertionError: expected [21.92] but found [value]

您的值应该是21.92,现在如果您将像下面这样使用这个值进行测试,您的测试用例将通过。

 Assert.assertEquals(21.92, str);

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

为出

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

完整的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");
}
}

基于@dfa的回答另一个说明如何测试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()时,它将按照helper方法出现的顺序调用它们:

  1. setUpOutput(),因为有@Before注释
  2. provideInput(String data),从testCase1()调用
  3. getOutput(),从testCase1()调用
  4. restoreSystemInputOutput(),因为有@After注释

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

如果函数打印到系统。输出时,您可以使用系统捕获输出。setOut方法更改系统。输出到由你提供的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());

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