Тест 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());

Ответ 1

используя ByteArrayOutputStream и System.setXXX прост:

private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();

@Before
public void setUpStreams() {
    System.setOut(new PrintStream(outContent));
    System.setErr(new PrintStream(errContent));
}

@After
public void cleanUpStreams() {
    System.setOut(null);
    System.setErr(null);
}

Примеры тестовых случаев:

@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 выводит строку версии и т.д.)

Ответ 2

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

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

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

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

Ответ 3

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

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

Ответ 4

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

Обсуждение

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

Ответ 5

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

Ответ 6

Ответ на

@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 определены в том же файле.

Затем в вашем тесте вы можете импортировать static 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() );
            }
        });
}

Ответ 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");
}

Ответ 8

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

Ответ 9

Вы не можете напрямую печатать с помощью 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);

Ответ 10

для

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

для err

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