java - test - Prueba de JUnit para System.out.println()
run unit test java command line (12)
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 entrada a un programa y probar su salida.
Como referencia, uso JUnit 4.12.
Digamos que tenemos este programa que simplemente replica entrada a 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 JUnit ejecuta testCase1()
, llamará a los métodos auxiliares en el orden en que aparecen:
-
setUpOutput()
, debido a la anotación@Before
-
provideInput(String data)
, llamado desdetestCase1()
-
getOutput()
, llamado desdetestCase1()
-
restoreSystemInputOutput()
, debido a la anotación@After
No System.err
porque no lo necesitaba, pero debería ser fácil de implementar, similar a probar System.out
.
Necesito escribir pruebas de 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)
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, devuelve un null
y escribe algunas cosas en la salida estándar.
¿Hay alguna manera de afirmar la salida de la consola en JUnit? Para atrapar casos como:
System.out.println("match found: " + strExpr);
System.out.println("xml not well formed: " + e.getMessage());
Ejemplo completo de JUnit 5 para probar System.out
(reemplazar la parte cuándo):
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");
}
}
En lugar de redirigir System.out
, refactorizaría la clase que usa System.out.println()
al pasar un PrintStream
como colaborador y luego usar System.out
en producción y un Test Spy en la prueba. Es decir, use la inyección de dependencia para eliminar el uso directo del flujo 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 manera, la clase bajo prueba se puede probar mediante una refactorización simple, sin tener la necesidad de redireccionar indirectamente la salida estándar o la interceptación oscura con una regla del sistema.
La respuesta de @dfa es genial, así que fui un paso más allá para hacer posible probar bloques de salida.
Primero creé TestHelper
con un método captureOutput
que acepta la clase molesta CaptureTest
. El método captureOutput hace el trabajo de configurar y eliminar los flujos de salida. Cuando se llama a la implementación del método de test
de CaptureOutput
, tiene acceso a la salida generada para el bloque de prueba.
Fuente para 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 están definidos en el mismo archivo.
Luego, en su prueba, puede importar la captura de salida estática. 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() );
}
});
}
No desea redireccionar la secuencia de system.out porque eso redirecciona la JVM COMPLETA. Cualquier otra cosa que se ejecute en la JVM puede ensuciarse. Hay mejores maneras de probar la entrada / salida. Busque en los talones / mocks.
No puede imprimir directamente usando system.out.println o usando la api del registrador mientras usa JUnit . Pero si desea verificar cualquier valor, simplemente puede usar
Assert.assertEquals("value", str);
Se lanzará debajo del error de aserción:
java.lang.AssertionError: expected [21.92] but found [value]
Su valor debería ser 21.92. Ahora, si va a probar usando este valor, como pasará, su caso de prueba pasará.
Assert.assertEquals(21.92, str);
Puede configurar el flujo de impresión System.out a través de setOut() (y para in
y err
). ¿Puede redirigir esto a un flujo de impresión que registra a una cadena y luego inspeccionarlo? Ese parece ser el mecanismo más simple.
(Yo recomendaría, en algún momento, convertir la aplicación a algún marco de registro, pero sospecho que ya está al tanto de esto).
Sé que este es un hilo viejo, pero hay una buena biblioteca para hacer esto:
Ejemplo de la documentación:
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á interceptar System.exit(-1)
y otras cosas que una herramienta de línea de comandos debería probar.
Si estaba usando Spring Boot (mencionó que está trabajando con una aplicación antigua, por lo que probablemente no lo esté, pero podría ser de utilidad 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");
}
Un poco fuera del 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 @Rule
commons-testing 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 podía encontrar ninguna solución adecuada para mis propias necesidades.
- Solo los enlaces para
log4j
,log4j2
ylogback
están disponibles en este momento, pero estoy feliz de agregar más.
el uso de 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);
}
casos de prueba de muestra:
@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());
}
Utilicé este código para probar la opción de la línea de comandos (afirmando que -version genera la cadena de versión, etc., etc.)
Edición: las versiones anteriores de esta respuesta llamadas System.setOut(null)
después de las pruebas; Esta es la causa de que los comentaristas de NullPointerExceptions se refieren a.
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);
}