Test JUnit per System.out.println ()


370

Devo scrivere i test JUnit per una vecchia applicazione mal progettata e sta scrivendo molti messaggi di errore nell'output standard. Quando il getResponse(String request)metodo si comporta correttamente, restituisce una risposta 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);
}

Ma quando ottiene XML non valido o non capisce la richiesta, ritorna nulle scrive alcune cose nell'output standard.

Esiste un modo per affermare l'output della console in JUnit? Per catturare casi come:

System.out.println("match found: " + strExpr);
System.out.println("xml not well formed: " + e.getMessage());

Relativo a, ma non un duplicato di stackoverflow.com/questions/3381801/…
Raedwald

Risposte:


581

usare ByteArrayOutputStream e System.setXXX è semplice:

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

casi di test di esempio:

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

Ho usato questo codice per testare l'opzione della riga di comando (affermando che -version genera la stringa di versione, ecc. Ecc.)

Modifica: versioni precedenti di questa risposta richiamate System.setOut(null)dopo i test; Questa è la causa a cui fanno riferimento i commentatori di NullPointerExceptions.


Inoltre, ho usato JUnitMatchers per testare le risposte: assertThat (risultato, contieneString ("<richiesta: GetEmployeeByKeyResponse")); Grazie, dfa.
Mike Minicki,

3
Preferisco usare System.setOut (null) per ripristinare lo stream a quello che era quando è stata avviata la VM
tddmonkey

5
I javadocs non dicono nulla sulla possibilità di passare null a System.setOut o System.setErr. Sei sicuro che funzionerà su tutti i JRE?
Finnw,

55
Ho riscontrato a NullPointerExceptionin altri test dopo aver impostato un flusso di errori null come suggerito sopra (in java.io.writer(Object), chiamato internamente da un validatore XML). Suggerirei invece di salvare l'originale in un campo: oldStdErr = System.erre ripristinarlo nel @Aftermetodo.
Luke Usherwood,

6
Ottima soluzione Solo una nota per chiunque lo usi, potrebbe essere necessario tagliare () spazi bianchi / newline da outContent.
Allison,

102

So che questo è un vecchio thread, ma c'è una bella libreria per farlo:

Regole di sistema

Esempio dai documenti:

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

Ti permetterà anche di intercettare System.exit(-1)e altre cose per le quali uno strumento da riga di comando dovrebbe essere testato.


1
Questo approccio è irto di problemi perché il flusso di output standard è una risorsa condivisa utilizzata da tutte le parti del programma. È meglio utilizzare Dependency Injection per eliminare l'uso diretto del flusso di output standard: stackoverflow.com/a/21216342/545127
Raedwald,

30

Invece di reindirizzare System.out, rifarrei la classe che usa System.out.println()passando un PrintStreamcome collaboratore e poi usando System.outin produzione e una spia di prova nel test. Cioè, utilizzare Dependency Injection per eliminare l'uso diretto del flusso di output standard.

In produzione

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

Nel test

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

Discussione

In questo modo la classe sottoposta a test diventa testabile da un semplice refactoring, senza la necessità di reindirizzamento indiretto dell'output standard o oscura intercettazione con una regola di sistema.


1
Impossibile trovare questo ConsoleWriter in qualsiasi punto del JDK: dov'è?
Jean-Philippe Caruana,

3
Probabilmente dovrebbe essere menzionato nella risposta, ma credo che la classe sia stata creata dall'utente1909402.
Sebastian,

6
Penso che ConsoleWritersia il soggetto del test,
Niel de Wet il

22

È possibile impostare il flusso di stampa System.out tramite setOut () (e per ine err). Puoi reindirizzare questo verso un flusso di stampa che registra su una stringa e quindi controllarlo? Sembrerebbe essere il meccanismo più semplice.

(A un certo punto raccomanderei di convertire l'app in un framework di registrazione, ma sospetto che tu ne sia già a conoscenza!)


1
È stato qualcosa che mi è venuto in mente, ma non potevo credere che non ci fosse un modo standard di JUnit per farlo. Grazie, cervello. Ma i crediti sono arrivati ​​alla Dfa per lo sforzo reale.
Mike Minicki,

Questo approccio è irto di problemi perché il flusso di output standard è una risorsa condivisa utilizzata da tutte le parti del programma. È meglio utilizzare Dependency Injection per eliminare l'uso diretto del flusso di output standard: stackoverflow.com/a/21216342/545127
Raedwald,

Sì. Vorrei secondo ciò e forse anche mettere in dubbio un'asserzione di registrazione (meglio asserire una chiamata su un componente di registrazione o simile)
Brian Agnew,

13

Leggermente fuori tema, ma nel caso in cui alcune persone (come me, quando ho scoperto questo thread) potrebbero essere interessate a catturare l'output del log tramite SLF4J, JUnit di test comuni@Rule potrebbe aiutare:

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

Disclaimer :

  • Ho sviluppato questa libreria poiché non sono riuscito a trovare alcuna soluzione adatta alle mie esigenze.
  • Solo binding per log4j, log4j2e logbacksono disponibili in questo momento, ma sono felice di aggiungere altro.

Grazie mille per aver creato questa libreria! Ho cercato qualcosa di simile per così tanto tempo! È molto, molto utile perché a volte semplicemente non puoi semplificare il tuo codice abbastanza da essere facilmente testabile, ma con un messaggio di registro puoi fare meraviglie!
carlspring,

Sembra davvero promettente ... ma anche quando copio il tuo programma ATMTest ed eseguo un test in Gradle, ricevo un'eccezione ... Ho sollevato un problema sulla tua pagina Github ...
mike rodent

9

La risposta @dfa è ottima, quindi ho fatto un passo ulteriore per rendere possibile il test dei blocchi di uscita.

Prima ho creato TestHelpercon un metodo captureOutputche accetta la classe fastidiosa CaptureTest. Il metodo captureOutput fa il lavoro di impostazione e abbattimento dei flussi di output. Quando l'attuazione del CaptureOutput's testmetodo viene chiamato, ha accesso all'uscita generare per blocco di prova.

Fonte per 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;
}

Si noti che TestHelper e CaptureTest sono definiti nello stesso file.

Quindi, nel test, è possibile importare la cattura statica output. Ecco un esempio usando 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

Se stavi usando Spring Boot (hai detto che stai lavorando con una vecchia applicazione, quindi probabilmente non lo sei, ma potrebbe essere utile ad altri), allora potresti usare org.springframework.boot.test.rule.OutputCapture nel modo seguente:

@Rule
public OutputCapture outputCapture = new OutputCapture();

@Test
public void out() {
    System.out.print("hello");
    assertEquals(outputCapture.toString(), "hello");
}

1
Ho votato a favore della tua risposta perché utilizzo Spring boot e mi ha impostato sulla strada giusta. Grazie! Tuttavia, outputCapture deve essere inizializzato. (public OutputCapture outputCapture = new OutputCapture ();) Vedi docs.spring.io/spring-boot/docs/current/reference/html/…
EricGreg

Hai assolutamente ragione. Grazie per il commento! Ho aggiornato la mia risposta.
Disperare il

4

Basato sulla risposta di @ dfa e di un'altra risposta che mostra come testare System.in , vorrei condividere la mia soluzione per fornire un input a un programma e testarne l'output.

Come riferimento, utilizzo JUnit 4.12.

Diciamo che abbiamo questo programma che replica semplicemente l'input in output:

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

Per testarlo, possiamo usare la seguente classe:

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

Non spiegherò molto, perché credo che il codice sia leggibile e ho citato le mie fonti.

Quando viene eseguito JUnit testCase1(), chiamerà i metodi di supporto nell'ordine in cui compaiono:

  1. setUpOutput(), a causa @Beforedell'annotazione
  2. provideInput(String data), chiamato da testCase1()
  3. getOutput(), chiamato da testCase1()
  4. restoreSystemInputOutput(), a causa @Afterdell'annotazione

Non ho testato System.errperché non ne avevo bisogno, ma dovrebbe essere facile da implementare, simile ai test System.out.


1

Non si desidera reindirizzare lo stream system.out perché reindirizza per la JVM COMPLETA. Qualsiasi altra cosa in esecuzione sulla JVM può essere incasinata. Esistono modi migliori per testare input / output. Guarda in stub / beffe.


1

Esempio completo di JUnit 5 da testare System.out(sostituire la parte quando):

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

Non è possibile stampare direttamente utilizzando system.out.println o utilizzando il logger api mentre si utilizza JUnit . Ma se vuoi controllare qualsiasi valore, puoi semplicemente usarlo

Assert.assertEquals("value", str);

Verrà generato l'errore di asserzione seguente:

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

Il tuo valore dovrebbe essere 21.92, ora se testerai usando questo valore come sotto il tuo caso di test passerà.

 Assert.assertEquals(21.92, str);

0

per fuori

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

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

Per questo tipo di logica di installazione e smontaggio, userei un @Rule, piuttosto che farlo in linea nel test. In particolare, se la tua affermazione fallisce, la seconda System.setOut/Errchiamata non verrà raggiunta.
dimo414,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.