Prueba de JUnit con entrada de usuario simulada

Estoy intentando crear algunas pruebas JUnit para un método que requiere la entrada del usuario. El método bajo prueba se parece un poco al siguiente método:

public static int testUserInput() { Scanner keyboard = new Scanner(System.in); System.out.println("Give a number between 1 and 10"); int input = keyboard.nextInt(); while (input  10) { System.out.println("Wrong number, try again."); input = keyboard.nextInt(); } return input; } 

¿Existe una forma posible de pasar automáticamente el progtwig un int en lugar de hacerlo yo o alguien más haciendo esto manualmente en el método de prueba JUnit? Como simular la entrada del usuario?

Gracias por adelantado.

Puede reemplazar System.in con su propio flujo llamando a System.setIn (InputStream in) . La secuencia de entrada puede ser una matriz de bytes:

 ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes()); System.setIn(in); // do your thing // optionally, reset System.in to its original System.setIn(System.in) 

Un enfoque diferente puede hacer que este método sea más comprobable al pasar IN y OUT como parámetros:

 public static int testUserInput(InputStream in,PrintStream out) { Scanner keyboard = new Scanner(in); out.println("Give a number between 1 and 10"); int input = keyboard.nextInt(); while (input < 1 || input > 10) { out.println("Wrong number, try again."); input = keyboard.nextInt(); } return input; } 

Para probar su código, debe crear un contenedor para las funciones de entrada / salida del sistema. Puedes hacer esto usando dependency injection, dándonos una clase que puede pedir nuevos enteros:

 public static class IntegerAsker { private final Scanner scanner; private final PrintStream out; public IntegerAsker(InputStream in, PrintStream out) { scanner = new Scanner(in); this.out = out; } public int ask(String message) { out.println(message); return scanner.nextInt(); } } 

Entonces puedes crear pruebas para tu función, usando un marco simulado (yo uso Mockito):

 @Test public void getsIntegerWhenWithinBoundsOfOneToTen() throws Exception { IntegerAsker asker = mock(IntegerAsker.class); when(asker.ask(anyString())).thenReturn(3); assertEquals(getBoundIntegerFromUser(asker), 3); } @Test public void asksForNewIntegerWhenOutsideBoundsOfOneToTen() throws Exception { IntegerAsker asker = mock(IntegerAsker.class); when(asker.ask("Give a number between 1 and 10")).thenReturn(99); when(asker.ask("Wrong number, try again.")).thenReturn(3); getBoundIntegerFromUser(asker); verify(asker).ask("Wrong number, try again."); } 

Luego escribe tu función que pase las pruebas. La función es mucho más limpia ya que puede eliminar la duplicación de enteros que piden / reciben y las llamadas al sistema reales están encapsuladas.

 public static void main(String[] args) { getBoundIntegerFromUser(new IntegerAsker(System.in, System.out)); } public static int getBoundIntegerFromUser(IntegerAsker asker) { int input = asker.ask("Give a number between 1 and 10"); while (input < 1 || input > 10) input = asker.ask("Wrong number, try again."); return input; } 

Esto puede parecer exagerado para su pequeño ejemplo, pero si está creando una aplicación más grande que se desarrolle así, puede pagar bastante rápido.

Una forma común de probar un código similar sería extraer un método que acepte un escáner y un PrintWriter, similar a esta respuesta de StackOverflow , y probar que:

 public void processUserInput() { processUserInput(new Scanner(System.in), System.out); } /** For testing. Package-private if possible. */ public void processUserInput(Scanner scanner, PrintWriter output) { output.println("Give a number between 1 and 10"); int input = scanner.nextInt(); while (input < 1 || input > 10) { output.println("Wrong number, try again."); input = scanner.nextInt(); } return input; } 

Tenga en cuenta que no podrá leer su salida hasta el final, y tendrá que especificar toda su entrada por adelantado:

 @Test public void shouldProcessUserInput() { StringWriter output = new StringWriter(); String input = "11\n" // "Wrong number, try again." + "10\n"; assertEquals(10, systemUnderTest.processUserInput( new Scanner(input), new PrintWriter(output))); assertThat(output.toString(), contains("Wrong number, try again."));); } 

Por supuesto, en lugar de crear un método de sobrecarga, también podría mantener el “escáner” y “salida” como campos mutables en su sistema bajo prueba. Tiendo a querer mantener las clases lo más apareadas posible, pero esa no es una gran concesión si le importa a usted o a sus compañeros de trabajo / instructor.

También puede elegir poner el código de prueba en el mismo paquete Java que el código bajo prueba (incluso si está en una carpeta de origen diferente), lo que le permite relajar la visibilidad de la sobrecarga de dos parámetros para que sea de paquete privado.

Puede comenzar extrayendo la lógica que recupera el número del teclado en su propio método. Luego puede probar la lógica de validación sin preocuparse por el teclado. Para probar la llamada keyboard.nextInt (), puede considerar el uso de un objeto simulado.

Encontré útil crear una interfaz que defina métodos similares a java.io.Console y luego usarlos para leer o escribir en System.out. La implementación real delegará a System.console () mientras que su versión JUnit puede ser un objeto simulado con entrada enlatada y respuestas esperadas.

Por ejemplo, construiría una MockConsole que contuviera la entrada enlatada del usuario. La implementación simulada extraería una cadena de entrada de la lista cada vez que se llamaba readLine. También reuniría todo el resultado escrito en una lista de respuestas. Al final de la prueba, si todo salió bien, entonces toda su entrada se habría leído y se puede afirmar en la salida.

Logré encontrar una manera más simple. Sin embargo, debe usar la biblioteca externa System.rules por @Stefan Birkner

Acabo de tomar el ejemplo proporcionado allí, creo que no podría haber sido más simple 🙂

 import java.util.Scanner; public class Summarize { public static int sumOfNumbersFromSystemIn() { Scanner scanner = new Scanner(System.in); int firstSummand = scanner.nextInt(); int secondSummand = scanner.nextInt(); return firstSummand + secondSummand; } } Test import static org.junit.Assert.*; import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.*; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.TextFromStandardInputStream; public class SummarizeTest { @Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Test public void summarizesTwoNumbers() { systemInMock.provideLines("1", "2"); assertEquals(3, Summarize.sumOfNumbersFromSystemIn()); } } 

El problema sin embargo en mi caso mi segunda entrada tiene espacios y esto hace que toda la stream de entrada sea nula.

He solucionado el problema sobre la lectura desde stdin para simular una consola …

Mi problema era que me gustaría intentar escribir en JUnit probar la consola para crear un determinado objeto …

El problema es como todo lo que dices: ¿Cómo puedo escribir en el Stdin de la prueba JUnit?

Luego, en la universidad, aprendo las redirecciones, como dices System.setIn (InputStream), cambia el descriptor de archivo stdin y puedes escribir luego …

Pero hay un problema más que solucionar … el bloque de prueba JUnit en espera leído de su nuevo InputStream, por lo que necesita crear un hilo para leer desde el InputStream y desde la prueba JUnit Escribir hilo en el nuevo Stdin … Primero debe escribe en el Stdin porque si escribes más tarde para crear el hilo para leer de stdin probablemente tengas condiciones de carrera … puedes escribir en el InputStream antes de leer o puedes leer desde InputStream antes de escribir …

Este es mi código, mi habilidad en inglés es mala. Espero que todo lo que puedas entender sea el problema y la solución para simular la escritura en stdin desde la prueba de JUnit.

 private void readFromConsole(String data) throws InterruptedException { System.setIn(new ByteArrayInputStream(data.getBytes())); Thread rC = new Thread() { @Override public void run() { study = new Study(); study.read(System.in); } }; rC.start(); rC.join(); }