Тест JUnit для System.out.println ()

370

Мне нужно написать тесты 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());
Майк Миницки
источник
Связано, но не с дубликатом stackoverflow.com/questions/3381801/…
Raedwald

Ответы:

581

использовать 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
источник
Более того, я использовал JUnitMatchers для проверки ответов: assertThat (result, containsString ("<request: GetEmployeeByKeyResponse")); Спасибо, дфа.
Майк Миницки
3
Я предпочитаю использовать System.setOut (null), чтобы восстановить поток до того, который был при запуске
виртуальной машины
5
В javadoc ничего не говорится о возможности передать значение null в System.setOut или System.setErr. Вы уверены, что это будет работать на всех JRE?
финн
55
Я сталкивался с NullPointerExceptionдругими тестами после установки нулевого потока ошибок, как было предложено выше (внутри java.io.writer(Object), вызываемого изнутри валидатором XML). Вместо этого я бы предложил сохранить оригинал в поле oldStdErr = System.errи восстановить его в @Afterметоде.
Люк Ашервуд
6
Отличное решение. Просто примечание для тех, кто его использует, вам может понадобиться trim () пробел / символ новой строки из outContent.
Эллисон
102

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

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

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

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

Будет
источник
1
Этот подход чреват проблемами, поскольку стандартный поток вывода является общим ресурсом, используемым всеми частями вашей программы. Лучше использовать Dependency Injection, чтобы исключить прямое использование стандартного выходного потока: stackoverflow.com/a/21216342/545127
Raedwald
30

Вместо перенаправления System.outя бы реорганизовал класс, который использует System.out.println(), передав PrintStreamв качестве соавтора, а затем используя System.outв производстве и тестового шпиона в тесте. То есть используйте 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"));

обсуждение

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

user1909402
источник
1
Я не смог найти этот ConsoleWriter нигде в JDK: где он?
Жан-Филипп Каруана
3
Вероятно, это должно быть упомянуто в ответе, но я считаю, что класс был создан user1909402.
Себастьян
6
Я думаю ConsoleWriter, что испытуемый
Ниль де Мок
22

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

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

Брайан Агнью
источник
1
Это было то, что пришло мне в голову, но я не мог поверить, что не существует стандартного способа JUnit сделать это. Спасибо, Мозг. Но кредиты дошли до ДФА за реальные усилия.
Майк Миницки
Этот подход чреват проблемами, поскольку стандартный поток вывода является общим ресурсом, используемым всеми частями вашей программы. Лучше использовать Dependency Injection, чтобы исключить прямое использование стандартного выходного потока: stackoverflow.com/a/21216342/545127
Raedwald
Да. Я бы поддержал это и, возможно, даже поставил бы под сомнение утверждение ведения журнала (лучше назначить вызов компоненту ведения журнала или тому подобному)
Брайан Агнью,
13

Немного не по теме, но в случае, если некоторые люди (например, я, когда я впервые нашел эту тему) могут быть заинтересованы в захвате вывода журнала через 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доступны в данный момент, но я счастлив , чтобы добавить больше.
Марк Карре
источник
Большое спасибо за создание этой библиотеки! Я так долго искал что-то подобное! Это очень, очень полезно, поскольку иногда вы просто не можете упростить свой код настолько, чтобы его можно было легко проверить, но с помощью сообщения журнала вы можете творить чудеса!
Carlspring
Это выглядит действительно многообещающе ... но даже когда я просто копирую вашу программу ATMTest и запускаю ее как тест под Gradle, я получаю исключение ... Я поднял проблему на вашей странице Github ...
Майк Родент
9

Ответ @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() );
            }
        });
}
mguymon
источник
7

Если вы использовали 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");
}
Disper
источник
1
Я проголосовал за ваш ответ, потому что я использую загрузку Spring, и это поставило меня на правильный путь. Спасибо! Однако outputCapture необходимо инициализировать. (public OutputCapture outputCapture = new OutputCapture ();) См. docs.spring.io/spring-boot/docs/current/reference/html/…
EricGreg
Ты абсолютно прав. Спасибо за комментарий! Я обновил свой ответ.
Disper
4

Основываясь на ответе @ 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аннотации
  2. provideInput(String data)позвонил из testCase1()
  3. getOutput()позвонил из testCase1()
  4. restoreSystemInputOutput()из-за @Afterаннотации

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

Антонио Виниций Менезес Медей
источник
1

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

Сэм Джейкобс
источник
1

Полный пример 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");
    }
}
Йенс Пигса
источник
0

Вы не можете напрямую печатать, используя 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);
AFFY
источник
0

за

@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вызов не будет достигнут.
dimo414