Тест JUnit для System.out.println () - PullRequest
326 голосов
/ 13 июля 2009

Мне нужно написать тесты 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());

Ответы [ 12 ]

518 голосов
/ 13 июля 2009

с использованием 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.

95 голосов
/ 02 марта 2013

Я знаю, что это старая ветка, но для этого есть хорошая библиотека:

Системные правила

Пример из документов:

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) и другие вещи, для которых необходимо будет протестировать инструмент командной строки.

22 голосов
/ 19 января 2014

Вместо перенаправления 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"));

Обсуждение

Таким образом, тестируемый класс становится тестируемым путем простого рефакторинга, без необходимости косвенного перенаправления стандартного вывода или неясного перехвата с системным правилом.

22 голосов
/ 13 июля 2009

Вы можете установить поток печати System.out с помощью setOut () (и для in и err). Можете ли вы перенаправить это в поток печати, который записывает в строку, а затем проверить это? Это кажется самым простым механизмом.

(Я бы рекомендовал на каком-то этапе преобразовать приложение в какую-то инфраструктуру ведения журналов - но я подозреваю, что вы уже знаете об этом!)

12 голосов
/ 11 декабря 2014

Немного не по теме, но в случае, если некоторые люди (например, я, когда я впервые нашел эту тему) могут быть заинтересованы в захвате вывода журнала через 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, но я рад добавить еще.
9 голосов
/ 02 сентября 2012

@ dfa ответ отличный, поэтому я сделал еще один шаг, чтобы можно было проверить блоки вывода.

Сначала я создал TestHelper с методом captureOutput, который принимает раздражающий класс CaptureTest. Метод captureOutput выполняет установку и разбор выходных потоков. Когда вызывается реализация CaptureOutput метода 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() );
            }
        });
}
6 голосов
/ 27 февраля 2017

Если вы использовали 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");
}
3 голосов
/ 06 июня 2018

На основании ответа @ 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(), он будет вызывать вспомогательные методы в порядке их появления:

  1. setUpOutput(), из-за аннотации @Before 1023 *
  2. provideInput(String data), вызывается с testCase1()
  3. getOutput(), вызывается с testCase1()
  4. restoreSystemInputOutput(), из-за аннотации @After 1035 *

Я не тестировал System.err, потому что мне это не нужно, но это должно быть легко реализовать, подобно тестированию System.out.

1 голос
/ 31 мая 2018

Полный пример 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");
    }
}
1 голос
/ 13 октября 2012

Вы не хотите перенаправлять поток system.out, потому что он перенаправляет для ВСЕЙ JVM. Все, что работает на JVM, может испортиться. Есть лучшие способы для проверки ввода / вывода. Посмотрите на заглушки / издевательства.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...