Тест JUnit для System.out.println()
Мне нужно написать тесты 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());
15 ответов
Использовать 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.
Я знаю, что это старая ветка, но для этого есть хорошая библиотека:
Пример из документов:
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
в производстве и Test Spy в тесте. То есть используйте Dependency Injection, чтобы исключить прямое использование стандартного потока вывода.
В производстве
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"));
обсуждение
Таким образом, тестируемый класс становится тестируемым путем простого рефакторинга, без необходимости косвенного перенаправления стандартного вывода или неясного перехвата с системным правилом.
Вы можете установить поток печати System.out через setOut () (и для in
а также err
). Можете ли вы перенаправить это в поток печати, который записывает в строку, а затем проверить это? Это будет самый простой механизм.
(Я бы посоветовал на каком-то этапе преобразовать приложение в какую-то среду ведения журналов - но я подозреваю, что вы уже знаете об этом!)
Немного не по теме, но в случае, если некоторые люди (например, я, когда я впервые нашел эту тему) могут быть заинтересованы в захвате вывода журнала через SLF4J, JUnit для commons-testing@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
доступны на данный момент, но я рад добавить еще.
Если вы использовали 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");
}
Ответ @dfa отличный, поэтому я сделал еще один шаг, чтобы можно было проверить блоки вывода.
Сначала я создал TestHelper
с методом captureOutput
который принимает раздражающий класс CaptureTest
, Метод captureOutput выполняет установку и разбор выходных потоков. Когда реализация CaptureOutput
"s test
вызывается метод, он имеет доступ к генерации вывода для тестового блока.
Источник для 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() );
}
});
}
Основываясь на ответе @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()
, он будет вызывать вспомогательные методы в порядке их появления:
setUpOutput()
из-за@Before
аннотированиеprovideInput(String data)
позвонил изtestCase1()
getOutput()
позвонил изtestCase1()
restoreSystemInputOutput()
из-за@After
аннотирование
Я не тестировал System.err
потому что мне это не нужно, но это должно быть легко реализовать, как в тестировании System.out
,
Если вы используете JUnit 5 с API Jupiter, я настоятельно рекомендую попробовать библиотеку junit-pioneer . Он обеспечивает надежное решение для чтения со стандартного ввода (System.in
) и запись в стандартный вывод ( и ). Например, вы можете использовать его следующим образом:
@Test
@StdIo
void out(StdOut out) {
System.out.print("hello");
assertEquals("hello", out.capturedLines()[0]);
}
@Test
@StdIo
public void err(StdErr err) {
System.err.print("hello again");
assertEquals("hello again", err.capturedLines()[0]);
}
В этом случае нет необходимости вручную переопределятьSystem.out
иSystem.err
. Более того, используяStdIo
,StdOut
,StdIn
может помочь избежать потенциальных проблем параллелизма при параллельном запуске тестов. Более подробную информацию о других вариантах использования вы можете найти в официальной документации .
Зависимость:
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>2.0.1</version>
<scope>test</scope>
</dependency>
Полный пример JUnit 5 для тестирования System.out
(заменить часть когда):
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, вы можете захватить этот вывод, используя метод System.setOut, чтобы изменить System.out для перехода к предоставленному вами PrintStream. Если вы создаете PrintStream, подключенный к ByteArrayOutputStream, вы можете записать вывод в виде String.
// 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());
За
@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);
}
Вы не хотите перенаправлять поток system.out, потому что он перенаправляет для ВСЕЙ JVM. Все, что работает на JVM, может испортиться. Есть лучшие способы для проверки ввода / вывода. Посмотрите на заглушки / издевательства.
Хотя этот вопрос очень старый и на него уже есть очень хорошие ответы, я хочу предложить альтернативу. Мне понравился ответ
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();
}
}
Вы не можете напрямую печатать, используя system.out.println или используя logger api при использовании JUnit. Но если вы хотите проверить какие-либо значения, вы можете просто использовать
Assert.assertEquals("value", str);
Будет выдано сообщение об ошибке ниже:
java.lang.AssertionError: expected [21.92] but found [value]
Ваше значение должно быть 21,92. Теперь, если вы будете тестировать, используя это значение, как показано ниже, ваш тестовый пример пройдет.
Assert.assertEquals(21.92, str);