Test JUnit pour System.out.println ()

Je dois é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(Ssortingng 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() { Ssortingng request = "request"; Ssortingng expResult = "response"; Ssortingng result = instance.getResponse(request); assertEquals(expResult, result); } 

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

Existe-t-il un moyen 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()); 

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

    exemple de cas de test:

     @Test public void out() { System.out.print("hello"); assertEquals("hello", outContent.toSsortingng()); } @Test public void err() { System.err.print("hello again"); assertEquals("hello again", errContent.toSsortingng()); } 

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

    Edit: Les versions précédentes de cette réponse appelaient System.setOut(null) après les tests; C’est la cause de la référence aux commentaires NullPointerExceptions.

    Je sais que c’est un vieux thread, mais il y a une belle bibliothèque pour faire ceci:

    Règles du système

    Exemple des documents:

     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 qu’un outil de ligne de commande devra être testé.

    Vous pouvez définir le stream d’impression System.out via setOut () (et pour in et err ). Pouvez-vous le redirect vers un stream d’impression qui enregistre dans une chaîne, puis l’inspecter? Cela semble être le mécanisme le plus simple.

    (Je préconiserais, à un moment donné, de convertir l’application en une structure de journalisation – mais je soupçonne que vous en êtes déjà conscient!)

    Au lieu de redirect System.out , je reformulerais la classe qui utilise System.out.println() en passant un PrintStream tant que collaborateur, puis en utilisant System.out en production et un Test Spy dans le test.

    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.toSsortingng(), is("expected output")); 

    Discussion

    De cette manière, la classe testée est testable par un simple refactoring, sans avoir besoin de redirect indirectement la sortie standard ou de faire une interception obscure avec une règle système.

    Un peu hors sujet, mais dans le cas où certaines personnes (comme moi, quand j’ai trouvé ce sujet) pourraient être intéressées par la capture de la sortie du journal via SLF4J, JUnit @Rule 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)); } } 

    Disclaimer :

    • J’ai développé cette bibliothèque car je ne trouvais aucune 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 append d’autres.

    @dfa answer is great, j’ai donc pris une mesure plus importante pour pouvoir tester des blocs de sortie.

    J’ai d’abord créé TestHelper avec une méthode captureOutput qui accepte la classe CaptureTest . La méthode captureOutput permet de définir et de supprimer les stream de sortie. Lorsque l’implémentation de la méthode de test de CaptureOutput est appelée, elle a access à 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 le 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.toSsortingng() ); } }); } 

    Si vous utilisiez Spring Boot (vous avez mentionné que vous travaillez avec une ancienne application, de sorte que vous ne l’êtes probablement pas, mais que cela pourrait être utile aux 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.toSsortingng(), "hello"); } 

    Vous ne voulez pas redirect le stream system.out car cela redirige pour la JVM ENTIRE. Tout ce qui tourne sur la JVM peut être perturbé. Il existe de meilleurs moyens de tester les entrées / sorties. Rechercher dans les talons / mocks.

    Vous ne pouvez pas imprimer directement en utilisant system.out.println ou en utilisant l’ API logger lors de l’utilisation de JUnit . Mais si vous voulez vérifier les valeurs, vous pouvez simplement utiliser

     Assert.assertEquals("value", str); 

    Il jettera sous l’erreur d’assertion:

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

    Votre valeur devrait être 21,92. Maintenant, si vous testez en utilisant cette valeur, comme ci-dessous, votre test élémentaire passera.

      Assert.assertEquals(21.92, str); 

    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.toSsortingng()); 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.toSsortingng()); System.setErr(save_err); } 

    Exemple JUnit 5 complet pour tester System.out (remplacez 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.toSsortingng()).containsIgnoringCase("example"); } } 

    Sur la base de la réponse de @dfa et d’ une autre réponse qui montre comment tester System.in , j’aimerais partager ma solution pour donner une consortingbution à un programme et tester sa sortie.

    Pour référence, j’utilise JUnit 4.12.

    Disons que nous avons ce programme qui reproduit simplement les entrées en sortie:

     import java.util.Scanner; public class SimpleProgram { public static void main(Ssortingng[] 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(Ssortingng data) { testIn = new ByteArrayInputStream(data.getBytes()); System.setIn(testIn); } private Ssortingng getOutput() { return testOut.toSsortingng(); } @After public void restoreSystemInputOutput() { System.setIn(systemIn); System.setOut(systemOut); } @Test public void testCase1() { final Ssortingng testSsortingng = "Hello!"; provideInput(testSsortingng); SimpleProgram.main(new Ssortingng[0]); assertEquals(testSsortingng, getOutput()); } } 

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

    Lorsque JUnit exécute testCase1() , il va appeler les méthodes d’assistance dans l’ordre dans testCase1() elles apparaissent:

    1. setUpOutput() , en raison de l’annotation @Before
    2. provideInput(Ssortingng data) , appelé depuis testCase1()
    3. getOutput() , appelé depuis testCase1()
    4. restoreSystemInputOutput() , en raison de l’annotation @After

    Je n’ai pas testé System.err parce que je n’en avais pas besoin, mais il devrait être facile à implémenter, tout comme le test System.out .