Test JUnit dla systemu.Wynocha.println()

Muszę napisać testy JUnit dla starej aplikacji, która jest źle zaprojektowana i pisze wiele komunikatów o błędach na standardowe wyjście. Gdy metoda getResponse(String request) zachowuje się poprawnie, zwraca odpowiedź 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);
}

Ale gdy XML zostanie źle sformatowany lub nie zrozumie żądania, zwraca null i zapisuje pewne rzeczy na standardowe wyjście.

Czy jest jakiś sposób na potwierdzenie wyjścia konsoli w JUnit? Do łapania takich przypadków jak:

System.out.println("match found: " + strExpr);
System.out.println("xml not well formed: " + e.getMessage());
Author: Bonifacio2, 2009-07-13

12 answers

Za pomocą ByteArrayOutputStream i systemu.setXXX jest prosty:

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

Przykładowe przypadki testowe:

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

Użyłem tego kodu do przetestowania opcji wiersza poleceń (twierdząc, że-version wyświetla ciąg wersji, itd itp)

Edit: Wcześniejsze wersje tej odpowiedzi nazwane System.setOut(null) po testach; jest to przyczyna NullPointerExceptions, do której odnoszą się komentatorzy.

 476
Author: dfa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-21 10:22:13

Wiem, że to stary wątek, ale jest do tego fajna biblioteka:

Zasady Systemu

Przykład z dokumentów:

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

Pozwoli również na pułapkę System.exit(-1) i inne rzeczy, dla których narzędzie wiersza poleceń musi być przetestowane.

 84
Author: Will,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-25 07:11:14

Możesz ustawić System.out print stream poprzez setOut () (oraz dla in i err). Czy możesz przekierować to do strumienia drukowania, który zapisuje się na ciąg znaków, a następnie sprawdzić to ? To wydaje się być najprostszy mechanizm.

(chciałbym, na pewnym etapie, przekonwertować aplikację do jakiegoś frameworka logowania - ale podejrzewam, że już o tym wiesz!)

 21
Author: Brian Agnew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-13 13:19:46

Zamiast przekierowywać System.out, refaktorowałbym klasę, która używa System.out.println(), przekazując PrintStream jako współpracownika, a następnie używając System.outw produkcji i szpiega testowego W teście.

W Produkcji

ConsoleWriter writer = new ConsoleWriter(System.out));

W teście

ByteArrayOutputStream outSpy = new ByteArrayOutputStream();
ConsoleWriter writer = new ConsoleWriter(new PrintStream(outSpy));
writer.printSomething();
assertThat(outSpy.toString(), is("expected output"));

Dyskusja

W ten sposób testowana klasa staje się testowalna przez prostą refaktoryzację, bez potrzeby pośredniego przekierowania standardowego wyjścia lub przechwytywania za pomocą zasada systemowa.

 16
Author: user1909402,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-19 11:49:31

Nieco off topic, ale w przypadku, gdy niektórzy ludzie (jak ja, kiedy po raz pierwszy znalazłem ten wątek) mogą być zainteresowani przechwytywaniem logów przez SLF4J, JUnitcommons-testing może pomóc:

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

Zastrzeżenie :

  • stworzyłem tę bibliotekę, ponieważ nie mogłem znaleźć żadnego odpowiedniego rozwiązania dla moich własnych potrzeb.
  • tylko wiązania dla log4j, log4j2 i logback są dostępne w tej chwili, ale z przyjemnością dodam więcej.
 10
Author: Marc Carré,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-11 08:29:34

@Dfa odpowiedź jest świetna, więc zrobiłem krok dalej, aby umożliwić testowanie bloków ouput.

Najpierw stworzyłem {[2] } metodą captureOutput, która akceptuje klasę CaptureTest. Metoda captureOutput wykonuje pracę polegającą na ustawianiu i rozkładaniu strumieni wyjściowych. Po wywołaniu implementacji metody CaptureOutput s test, ma ona dostęp do danych wyjściowych generowanych dla bloku testowego.

Źródło dla 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;
}

Zauważ, że TestHelper i CaptureTest są zdefiniowane w tym samym pliku.

Następnie w teście możesz zaimportować static captureOutput. Oto przykład użycia 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
Author: mguymon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-02 06:07:04

Jeśli używasz Spring Boot (wspomniałeś, że pracujesz ze starą aplikacją, więc prawdopodobnie nie jesteś, ale może być przydatna dla innych), Możesz użyć org.springframework.but.test.zasada.OutputCapture w następujący sposób:

@Rule
public OutputCapture outputCapture = new OutputCapture();

@Test
public void out() {
    System.out.print("hello");
    assertEquals(outputCapture.toString(), "hello");
}
 6
Author: Disper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-25 07:47:16

Nie chcesz przekierowywać systemu.out stream, ponieważ przekierowuje dla całego JVM. Wszystko inne działa na JVM może się popsuć. Istnieją lepsze sposoby testowania wejścia/wyjścia. Zajrzyj do stubs/mocks.

 1
Author: Sam Jacobs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-13 09:10:53

Nie można bezpośrednio wydrukować za pomocą systemu .Wynocha.println lub używając logger api Podczas używania JUnit . Ale jeśli chcesz sprawdzić jakieś wartości, możesz po prostu użyć

Assert.assertEquals("value", str);

Rzuci poniżej błąd twierdzenia:

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

Twoja wartość powinna wynosić 21.92, teraz jeśli będziesz testował używając tej wartości, jak poniżej, Twój przypadek testowy przejdzie.

 Assert.assertEquals(21.92, str);
 1
Author: Aftab Virtual,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-19 05:56:34

Na zewnątrz

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

Dla 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);
}
 0
Author: Shimon Doodkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-04 20:15:18

Pełny przykład JUnit 5 do testu System.out (zastąp część when):

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
Author: Jens Piegsa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-31 12:31:32

Na podstawie @dfa ' s answer i kolejna odpowiedź, która pokazuje jak testować System.in , chciałbym podzielić się moim rozwiązaniem, aby dać wejście do programu i przetestować jego wyjście.

Jako odniesienie używam JUnit 4.12.

Załóżmy, że mamy ten program, który po prostu replikuje wejście do wyjścia:

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

Aby go przetestować, możemy użyć następującej klasy:

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

Niewiele wyjaśnię, ponieważ uważam, że kod jest czytelny i zacytowałem mój źródła.

Gdy JUnit uruchomi testCase1(), wywoła metody pomocnicze w kolejności, w jakiej się pojawiają:

  1. setUpOutput(), z powodu @Before adnotacji
  2. provideInput(String data), wywołane z testCase1()
  3. getOutput(), wywołane z testCase1()
  4. restoreSystemInputOutput(), z powodu @After adnotacji

Nie testowałem System.err, ponieważ go nie potrzebowałem, ale powinien być łatwy do wdrożenia, podobny do testowania System.out.

 0
Author: Antonio Vinicius Menezes Medei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-06 13:09:28