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

Я використовував цей код для тестування параметра командного рядка (стверджуючи, що -версія виводить рядок версії, тощо, тощо)

Редагувати: попередні версії цієї відповіді викликали System.setOut(null)після тестів; Це причина, на яку посилаються коментатори NullPointerExceptions.


Крім того, я використовував JUnitMatchers для тестування відповідей: assertThat (результат, міститьString ("<запит: GetEposleeeByKeyResponse")); Дякую, dfa.
Майк Мініцький

3
Я вважаю за краще використовувати System.setOut (null) для відновлення потоку назад до того, яким він був при запуску VM
tddmonkey

5
Javadocs нічого не говорять про можливість передати null до System.setOut або System.setErr. Ви впевнені, що це буде працювати на всіх JRE?
finnw

55
У NullPointerExceptionінших тестах я зіткнувся після встановлення нульового потоку помилок, як було запропоновано вище (у java.io.writer(Object), внутрішньо викликаному валідатором XML). Я б запропонував замість цього зберегти оригінал у полі: oldStdErr = System.errта відновити це у @Afterметоді.
Люк Ушервуд

6
Прекрасне рішення. Просто примітка для тих, хто її використовує, можливо, вам доведеться обрізати () пробіл / новий рядок від 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(), передаючи a 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"));

Обговорення

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


1
Я не міг знайти цього ConsoleWriter ніде в JDK: де це?
Жан-Філіппе Каруана

3
Це, мабуть, слід сказати у відповіді, але я вважаю, що клас створений користувачем1909402.
Себастьян

6
Я думаю, що ConsoleWriterце тест,
Ніль де Мокрий

22

Ви можете встановити потік друку System.out через setOut () (і для inі err). Чи можете ви перенаправити це на потік друку, який записує на рядок, а потім перевірити це? Це, здавалося б, був найпростішим механізмом.

(Я б закликав на певному етапі перетворити додаток на якийсь фреймворк, але я підозрюю, що ви вже знаєте про це!)


1
Це було щось, що мені прийшло в голову, але я не міг повірити, що немає стандартного способу JUnit для цього. Спасибі, Мозок. Але кредити дісталися до dfa за фактичні зусилля.
Майк Мініцький

Такий підхід загрожує проблемами, оскільки стандартний вихідний потік - це спільний ресурс, який використовується всіма частинами вашої програми. Краще використовувати Dependency Injection , щоб виключити пряме використання стандартного вихідного потоку: stackoverflow.com/a/21216342/545127
Raedwald

Так. Я б задумав, що, можливо, навіть поставив під сумнів твердження про реєстрацію (краще заявити виклик на компонент журналу чи подібне)
Брайан Агнев

13

Трохи поза темою, але у випадку, якщо деякі люди (як я, коли я вперше знайшов цю тему) можуть бути зацікавлені у фіксації виходу журналу через SLF4J, 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є на даний момент, але я радий додати більше.

Дуже дякую за створення цієї бібліотеки! Я так довго шукав щось подібне! Це дуже, дуже корисно, оскільки іноді ви просто не можете спростити свій код, щоб бути легко перевіреним, але за допомогою повідомлення журналу ви можете робити чудеса!
carlspring

Це виглядає дуже перспективно ... але навіть коли я просто копіюю вашу програму ATMTest і запускаю її як тест під Gradle, я отримую виняток ... Я порушив проблему на вашій сторінці Github ...
Майк гризун

9

@dfa відповідь чудова, тому я зробив крок далі, щоб можна було протестувати блоки виходу.

Спершу я створив TestHelperметод, captureOutputякий приймає роздратований клас CaptureTest. Метод catchOutput виконує роботу з налаштування та виведення вихідних потоків. Коли викликається реалізація методу CaptureOutputs 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 визначені в одному файлі.

Тоді у вашому тесті ви можете імпортувати статичний catchOutput. Ось приклад використання 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");
}

1
Я відповідав за вашу відповідь, тому що я використовую Spring boot, і це поставило мене на правильний шлях. Дякую! Однак outputCapture потребує ініціалізації. (public OutputCapture outputCapture = новий OutputCapture ();) Див. docs.spring.io/spring-boot/docs/current/reference/html/…
EricGreg

Ви абсолютно праві. Дякуємо за коментар! Я оновив свою відповідь.
Діспер

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 або використовуючи реєстратор api під час використання JUnit . Але якщо ви хочете перевірити будь-які значення, то ви можете просто скористатися

Assert.assertEquals("value", str);

Це призведе до помилки твердження нижче:

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

Ваша цінність повинна становити 21,92. Тепер, якщо ви протестуєте, використовуючи це значення, як нижче, тестовий випадок пройде.

 Assert.assertEquals(21.92, str);

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.