2009-07-13 8 views
265

Je dois écrire des tests JUnit pour une ancienne application qui est mal conçue et qui écrit de nombreux messages d'erreur sur la sortie standard. Lorsque la méthode getResponse(String request) se comporte correctement, il renvoie une réponse XML:JUnit test for System.out.println()

@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 XML malformé ou ne comprend pas la demande, il retourne null et écrit des choses 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()); 
+0

liés, mais pas un doublon de http://stackoverflow.com/questions/3381801/how-do-i-unit-test-saving-file-to-the-disk – Raedwald

Répondre

445

utilisant ByteArrayOutputStream et System.setXXX est simple:

private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); 
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); 

@Before 
public void setUpStreams() { 
    System.setOut(new PrintStream(outContent)); 
    System.setErr(new PrintStream(errContent)); 
} 

@After 
public void restoreStreams() { 
    System.setOut(System.out); 
    System.setErr(System.err); 
} 

cas de test de l'échantillon:

@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 (affirmant que la chaîne -version sorties de version, etc etc)

Édition: Les versions antérieures de cette réponse appelées System.setOut(null) après les tests; C'est la cause des commentateurs de NullPointerExceptions.

+0

De plus, j'ai utilisé JUnitMatchers pour tester les réponses: assertThat (result, containsString ("

+3

Je préfère utiliser System.setOut (null) pour restaurer le flux à ce qu'il était lorsque la machine virtuelle a été lancée – tddmonkey

+5

Les javadocs ne disent rien sur la possibilité de passer null à System.setOut ou System.setErr. Êtes-vous sûr que cela fonctionnera sur tous les JRE? – finnw

19

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 inspecter cela? Cela semblerait être le mécanisme le plus simple.

(! Je préconiserais, à un certain moment, convertir l'application à un certain cadre de l'exploitation forestière - mais je suppose que vous êtes déjà au courant de cette)

+1

C'était quelque chose qui est venu à mon avis, mais je ne pouvais pas croire qu'il n'y ait pas de moyen standard pour le faire. Merci, Cerveau. Mais les crédits sont arrivés à dfa pour l'effort réel. –

7

@dfa réponse est super, donc j'ai fait un pas de plus pour permettre de tester des blocs de sortie.

D'abord j'ai créé TestHelper avec une méthode captureOutput qui accepte la classe ennuyeuse CaptureTest. La méthode captureOutput effectue le travail de définition et de destruction des flux de sortie. Lorsque l'implémentation de la méthode test de CaptureOutput 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 le fichier captureOutput statique. Voici un exemple 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()); 
      } 
     }); 
} 
2

Vous ne voulez pas rediriger le flux system.out parce que réoriente pour la machine virtuelle Java ENTIER. Tout ce qui fonctionne sur la JVM peut être foiré. Il existe de meilleurs moyens de tester les entrées/sorties. Regardez dans les talons/mock.

79

Je sais que c'est un vieux fil, mais il y a une belle bibliothèque à faire:

System Rules

Exemple des docs:

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

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

15

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

dans la 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 un refactoring simple, sans avoir la besoin d'indirecte redirection de la sortie standard ou de l'interception obscure avec une règle système.

+1

Je ne pouvais pas trouver ce ConsoleWriter n'importe où dans le JDK: où est-ce? –

+2

Il devrait probablement être mentionné dans la réponse, mais je crois que cette classe a été créée par user1909402. – Sebastian

+3

Je pense que 'ConsoleWriter' est le sujet du test, –

10

légèrement hors sujet, mais dans le cas où certaines personnes (comme moi, quand je trouvé ce fil) pourraient être intéressés à capturer la sortie du journal via SLF4J, JUnit de commons-testing@Rule 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 pouvais pas trouver de solution adaptée à mes besoins.
  • Seules les fixations pour log4j, log4j2 et logback sont disponibles pour le moment, mais je suis heureux d'en ajouter d'autres.
+0

Merci beaucoup pour la création de cette bibliothèque! Je cherchais quelque chose comme ça depuis si longtemps! C'est très, très utile car parfois vous ne pouvez simplement pas simplifier votre code pour être facilement testable, mais avec un message de log vous pouvez faire des merveilles! – carlspring

+0

Cela semble très prometteur ... mais même quand je copie juste votre programme ATMTest et l'exécute comme un test sous Gradle, je reçois une exception ... J'ai soulevé un problème sur votre page Github ... –

1

Vous ne pouvez pas imprimer directement à l'aide System.out.println ou en utilisant enregistreur api tout en utilisant JUnit. Mais si vous voulez vérifier toutes les valeurs, vous pouvez simplement utiliser

Assert.assertEquals("value", str); 

Il jetteront ci-dessous erreur d'assertion:

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

Votre valeur doit être 21,92, maintenant si vous allez tester en utilisant cette valeur comme ci-dessous votre cas de test passera.

Assert.assertEquals(21.92, str); 
5

Si vous utilisez Boot Spring (vous avez dit que vous travaillez avec une ancienne application, de sorte que vous n'êtes probablement pas, mais il pourrait être utile à d'autres), vous pouvez ensuite 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"); 
} 
+1

I up-voté votre réponse parce que j'utilise Spring Boot et il m'a mis sur la bonne voie. Merci! Cependant, outputCapture doit être initialisé. (public OutputCapture outputCapture = new OutputCapture();) Voir https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-output-capture- test-utility – EricGreg

+0

Vous avez absolument raison. Merci pour le commentaire! J'ai mis à jour ma réponse. – Disper

0

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