web-dev-qa-db-fra.com

Test JUnit pour System.out.println ()

J'ai besoin d'écrire des tests JUnit pour une ancienne application mal conçue et qui écrit beaucoup de messages d'erreur sur la sortie standard. Lorsque la méthode getResponse(String request) se comporte correctement, elle renvoie une réponse 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);
}

Mais quand il obtient un XML mal formé ou ne comprend pas la requête, il retourne null et écrit des éléments sur la sortie standard.

Est-il possible d'affirmer la sortie de la console dans JUnit? Pour attraper des cas comme:

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

utiliser ByteArrayOutputStream et System.setXXX est simple:

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

échantillons de cas de test:

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

J'ai utilisé ce code pour tester l'option de ligne de commande (en affirmant que -version génère la chaîne de version, etc.).

Edit: Les versions précédentes de cette réponse appelée System.setOut(null) après les tests; C’est la cause de NullPointerExceptions à laquelle les commentateurs font référence.

528
dfa

Je sais que c'est un vieux fil, mais il existe une belle bibliothèque pour le faire:

Règles du système

Exemple tiré de la documentation:

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

Cela vous permettra également de piéger System.exit(-1) et d'autres éléments pour lesquels un outil de ligne de commande doit être testé.

97
Will

Au lieu de rediriger System.out, je refactoriserais la classe qui utilise System.out.println() en passant un PrintStream en tant que collaborateur, puis en utilisant System.out en production et un Testez Spy dans le test. Autrement dit, utilisez l’injection de dépendance pour éliminer l’utilisation directe du flux de sortie standard.

en production

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

dans le test

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

Discussion

De cette façon, la classe sous test devient testable par une simple refactorisation, sans avoir besoin de rediriger indirectement la sortie standard ni d’interception obscure avec une règle système.

25
user1909402

Vous pouvez définir le flux d'impression System.out via setOut () (et pour in et err). Pouvez-vous rediriger cela vers un flux d'impression qui enregistre dans une chaîne, puis inspectez-le? Cela semblerait être le mécanisme le plus simple.

(Je recommanderais, à un moment donné, de convertir l'application en un cadre de journalisation, mais je suppose que vous en êtes déjà conscient!)

22
Brian Agnew

Un peu en dehors du sujet, mais au cas où certaines personnes (comme moi, lorsque j'ai découvert ce fil de discussion) pourraient être intéressées par la capture de la sortie du journal via SLF4J, le JUnit @Rule de commons-testing pourrait aider:

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

Clause de non-responsabilité :

  • J'ai développé cette bibliothèque car je ne pouvais pas trouver de solution adaptée à mes propres besoins.
  • Seules les liaisons pour log4j, log4j2 et logback sont disponibles pour le moment, mais je suis heureux d'en ajouter d'autres.
12
Marc Carré

@dfa answer is great, j'ai donc fait un pas de plus pour permettre de tester des blocs de sortie.

J'ai d'abord créé TestHelper avec une méthode captureOutput qui accepte la classe agaçante CaptureTest. La méthode captureOutput effectue le travail de définition et de suppression des flux de sortie. Lorsque la méthode CaptureOutput de test est appelée, elle a accès à la sortie générée pour le bloc de test.

Source pour 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;
}

Notez que TestHelper et CaptureTest sont définis dans le même fichier.

Ensuite, dans votre test, vous pouvez importer la captureOutput statique. Voici un exemple d'utilisation de 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() );
            }
        });
}
9
mguymon

Si vous utilisiez Spring Boot (vous avez mentionné que vous travaillez avec une ancienne application, vous ne l'êtes probablement pas, mais cela pourrait être utile à d'autres), vous pouvez utiliser org.springframework.boot.test). .rule.OutputCapture de la manière suivante:

@Rule
public OutputCapture outputCapture = new OutputCapture();

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

Basé sur la réponse de @ dfa et ne autre réponse indiquant comment tester System.in , je voudrais partager ma solution pour donner une entrée à un programme et tester sa sortie.

En guise de référence, j'utilise JUnit 4.12.

Disons que nous avons ce programme qui réplique simplement les entrées vers les sorties:

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

Pour le tester, nous pouvons utiliser la classe suivante:

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

Je n'expliquerai pas grand chose, car je crois que le code est lisible et j'ai cité mes sources.

Lorsque JUnit exécute testCase1(), il appelle les méthodes d’aide dans l’ordre dans lequel elles apparaissent:

  1. setUpOutput(), à cause de l'annotation @Before
  2. provideInput(String data), appelé de testCase1()
  3. getOutput(), appelé de testCase1()
  4. restoreSystemInputOutput(), à cause de l'annotation @After

Je n'ai pas testé System.err parce que je n'en avais pas besoin, mais il devrait être facile à mettre en œuvre, comme pour tester System.out.

Exemple complet de JUnit 5 pour tester System.out (remplace la partie 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");
    }
}
1
Jens Piegsa

Vous ne souhaitez pas rediriger le flux system.out car cela redirige la JVM ENTIRE. Tout ce qui tourne sur la JVM peut être gâché. Il existe de meilleurs moyens de tester les entrées/sorties. Regardez dans les moignons/mocs.

1
Sam Jacobs

Vous ne pouvez pas imprimer directement en utilisant system.out.println ou en utilisant logger api en utilisant JUnit. Mais si vous voulez vérifier les valeurs, vous pouvez simplement utiliser

Assert.assertEquals("value", str);

Il va jeter ci-dessous l'erreur d'assertion:

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

Votre valeur doit être 21.92. Maintenant, si vous testez en utilisant cette valeur, comme ci-dessous, votre scénario de test réussira.

 Assert.assertEquals(21.92, str);
0
Virtual

pour sortir

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

pour 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
Shimon Doodkin