Prueba JUnit para System.out.println ()

370

Necesito escribir pruebas JUnit para una aplicación antigua que está mal diseñada y está escribiendo muchos mensajes de error en la salida estándar. Cuando el getResponse(String request)método se comporta correctamente, devuelve una respuesta 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);
}

Pero cuando obtiene un formato XML incorrecto o no comprende la solicitud, vuelve nully escribe algunas cosas en la salida estándar.

¿Hay alguna forma de afirmar la salida de la consola en JUnit? Para detectar casos como:

System.out.println("match found: " + strExpr);
System.out.println("xml not well formed: " + e.getMessage());
Mike Minicki
fuente
Relacionado con, pero no un duplicado de stackoverflow.com/questions/3381801/…
Raedwald

Respuestas:

581

utilizando ByteArrayOutputStream y System.setXXX es 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);
}

ejemplos de casos de prueba:

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

Usé este código para probar la opción de línea de comando (afirmando que -version genera la cadena de versión, etc., etc.)

Editar: versiones anteriores de esta respuesta llamadas System.setOut(null)después de las pruebas; Esta es la causa de que los comentaristas NullPointerExceptions se refieren.

dfa
fuente
Además, he usado JUnitMatchers para probar las respuestas: afirmar que (resultado, contieneString ("<solicitud: GetEmployeeByKeyResponse")); Gracias dfa.
Mike Minicki
3
Prefiero usar System.setOut (nulo) para restaurar la transmisión a lo que era cuando se lanzó la VM
tddmonkey el
55
Los javadocs no dicen nada acerca de poder pasar nulo a System.setOut o System.setErr. ¿Estás seguro de que esto funcionará en todos los JRE?
finnw
55
Encontré un NullPointerExceptionen otras pruebas después de configurar un flujo de error nulo como se sugirió anteriormente (en java.io.writer(Object), llamado internamente por un validador XML). En su lugar, sugeriría guardar el original en un campo: oldStdErr = System.erry restaurar esto en el @Aftermétodo.
Luke Usherwood
66
Gran solución Solo una nota para cualquiera que lo use, es posible que deba recortar () espacios en blanco / nueva línea de outContent.
Allison
102

Sé que este es un hilo viejo, pero hay una buena biblioteca para hacer esto:

Reglas del sistema

Ejemplo de los documentos:

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

También le permitirá atrapar System.exit(-1)y otras cosas por las que una herramienta de línea de comando necesitaría ser probada.

Será
fuente
1
Este enfoque está lleno de problemas porque la secuencia de salida estándar es un recurso compartido utilizado por todas las partes de su programa. Es mejor usar la inyección de dependencia para eliminar el uso directo del flujo de salida estándar: stackoverflow.com/a/21216342/545127
Raedwald
30

En lugar de redirigir System.out, refactorizaría la clase que usa System.out.println()al pasar a PrintStreamcomo colaborador y luego usar System.outen producción y un Spy de prueba en la prueba. Es decir, use la inyección de dependencia para eliminar el uso directo de la secuencia de salida estándar.

En producción

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

En la prueba

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

Discusión

De esta forma, la clase bajo prueba se vuelve comprobable mediante una simple refactorización, sin tener la necesidad de una redirección indirecta de la salida estándar o una intercepción oscura con una regla del sistema.

user1909402
fuente
1
No pude encontrar este ConsoleWriter en ninguna parte del JDK: ¿dónde está?
Jean-Philippe Caruana
3
Probablemente debería mencionarse en la respuesta, pero creo que esa clase fue creada por user1909402.
Sebastian
66
Creo que ConsoleWriteres el sujeto de prueba,
Niel de Wet
22

Puede configurar el flujo de impresión System.out a través de setOut () (y para iny err). ¿Puede redirigir esto a una secuencia de impresión que grabe en una cadena y luego inspeccionar eso? Ese parecería ser el mecanismo más simple.

(Yo recomendaría, en algún momento, convertir la aplicación en un marco de registro, ¡pero sospecho que ya lo sabe!)

Brian Agnew
fuente
1
Eso fue algo que me vino a la mente, pero no podía creer que no haya una forma estándar de JUnit para hacerlo. Gracias Brain. Pero los créditos llegaron a dfa por el esfuerzo real.
Mike Minicki
Este enfoque está lleno de problemas porque la secuencia de salida estándar es un recurso compartido utilizado por todas las partes de su programa. Es mejor usar la inyección de dependencia para eliminar el uso directo del flujo de salida estándar: stackoverflow.com/a/21216342/545127
Raedwald
Si. Lo respaldaría y tal vez incluso cuestionaría una afirmación de registro (mejor afirmar una llamada en un componente de registro o similar)
Brian Agnew
13

Ligeramente fuera de tema, pero en caso de que algunas personas (como yo, cuando encontré este hilo por primera vez) pudieran estar interesadas en capturar la salida del registro a través de SLF4J, JUnit de commons-testing@Rule podría ayudar:

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

Descargo de responsabilidad :

  • Desarrollé esta biblioteca ya que no pude encontrar ninguna solución adecuada para mis propias necesidades.
  • Solo hay enlaces para log4j, log4j2y logbackestán disponibles en este momento, pero me complace agregar más.
Marc Carré
fuente
¡Muchas gracias por crear esta biblioteca! ¡He estado buscando algo como esto por tanto tiempo! Es muy, muy útil ya que a veces simplemente no puede simplificar su código lo suficiente como para ser fácilmente comprobable, ¡pero con un mensaje de registro puede hacer maravillas!
carlspring
Esto parece muy prometedor ... pero incluso cuando solo copio su programa ATMTest y lo ejecuto como una prueba en Gradle, obtengo una excepción ... He planteado un problema en su página de Github ...
Mike rodent
9

La respuesta de @dfa es excelente, así que di un paso más para hacer posible probar bloques de salida.

Primero creé TestHelpercon un método captureOutputque acepta la clase anónima CaptureTest. El método captureOutput hace el trabajo de configurar y derribar las secuencias de salida. Cuando la ejecución de CaptureOutput's testmétodo se llama, tiene acceso a la salida de generar para el bloque de prueba.

Fuente de 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;
}

Tenga en cuenta que TestHelper y CaptureTest se definen en el mismo archivo.

Luego, en su prueba, puede importar la captura estática de salida. Aquí hay un ejemplo 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() );
            }
        });
}
mguymon
fuente
7

Si estaba usando Spring Boot (mencionó que está trabajando con una aplicación antigua, por lo que probablemente no lo sea, pero podría ser útil para otros), entonces podría usar org.springframework.boot.test.rule.OutputCapture en la siguiente manera:

@Rule
public OutputCapture outputCapture = new OutputCapture();

@Test
public void out() {
    System.out.print("hello");
    assertEquals(outputCapture.toString(), "hello");
}
Disper
fuente
1
Elegí tu respuesta porque uso Spring Boot y me puso en el camino correcto. ¡Gracias! Sin embargo, outputCapture necesita ser inicializado. (public OutputCapture outputCapture = new OutputCapture ();) Ver docs.spring.io/spring-boot/docs/current/reference/html/…
EricGreg
Estás absolutamente en lo correcto. ¡Gracias por el comentario! He actualizado mi respuesta.
Disper
4

Basado en la respuesta de @ dfa y otra respuesta que muestra cómo probar System.in , me gustaría compartir mi solución para dar una entrada a un programa y probar su salida.

Como referencia, uso JUnit 4.12.

Digamos que tenemos este programa que simplemente replica la entrada a la salida:

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

Para probarlo, podemos usar la siguiente clase:

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

No explicaré mucho, porque creo que el código es legible y cité mis fuentes.

Cuando se ejecuta JUnit testCase1(), llamará a los métodos de ayuda en el orden en que aparecen:

  1. setUpOutput(), debido a la @Beforeanotación
  2. provideInput(String data), llamado desde testCase1()
  3. getOutput(), llamado desde testCase1()
  4. restoreSystemInputOutput(), debido a la @Afteranotación

No probé System.errporque no lo necesitaba, pero debería ser fácil de implementar, similar a las pruebas System.out.

Antonio Vinicius Menezes Medei
fuente
1

No desea redirigir la secuencia system.out porque eso redirige para TODA la JVM. Cualquier otra cosa que se ejecute en la JVM puede estropearse. Hay mejores formas de probar la entrada / salida. Mira en trozos / simulacros.

Sam Jacobs
fuente
1

Ejemplo completo de JUnit 5 para probar System.out(reemplace la parte 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");
    }
}
Jens Piegsa
fuente
0

No puede imprimir directamente utilizando system.out.println o utilizando la api del registrador mientras utiliza JUnit . Pero si desea verificar cualquier valor, simplemente puede usar

Assert.assertEquals("value", str);

Lanzará el siguiente error de aserción:

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

Su valor debe ser 21.92. Ahora, si va a realizar la prueba con este valor, como se muestra a continuación, su caso de prueba pasará.

 Assert.assertEquals(21.92, str);
Affy
fuente
0

para salir

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

por 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);
}
Shimon Doodkin
fuente
Para este tipo de lógica de configuración y desmontaje, usaría un @Rule, en lugar de hacerlo en línea en su prueba. Notablemente, si su afirmación falla System.setOut/Err, no se alcanzará la segunda llamada .
dimo414