Estoy tratando de crear algunas pruebas JUnit para un método que requiere la entrada del usuario. El método bajo prueba se parece un poco al método siguiente:
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 < 1 || input > 10) {
System.out.println("Wrong number, try again.");
input = keyboard.nextInt();
}
return input;
}
¿Hay alguna forma posible de pasar automáticamente al programa un int en lugar de que yo o alguien más lo haga manualmente en el método de prueba JUnit? ¿Te gusta simular la entrada del usuario?
Gracias por adelantado.
java
eclipse
junit
user-input
Wimpey
fuente
fuente
Respuestas:
Puede reemplazar System.in con su propia secuencia llamando a System.setIn (InputStream in) . InputStream puede ser una matriz de bytes:
InputStream sysInBackup = System.in; // backup System.in to restore it later ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes()); System.setIn(in); // do your thing // optionally, reset System.in to its original System.setIn(sysInBackup);
Un enfoque diferente puede hacer que este método sea más comprobable pasando 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; }
fuente
\n
pero eso no hace la diferenciaByteArrayInputStream in = new ByteArrayInputStream(("1" + System.lineSeparator() + "2").getBytes());
para obtener múltiples entradas para diferenteskeyboard.nextLine()
llamadas.Para probar su código, debe crear un contenedor para las funciones de entrada / salida del sistema. Puede hacer esto usando la inyección de dependencia, dándonos una clase que puede solicitar 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(); } }
Luego puede crear pruebas para su 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 pasa las pruebas. La función es mucho más limpia, ya que puede eliminar la duplicación de enteros preguntando / obteniendo 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 excesivo para su pequeño ejemplo, pero si está construyendo una aplicación más grande, el desarrollo de este tipo puede resultar bastante rápido.
fuente
Una forma común de probar un código similar sería extraer un método que incluya un Scanner 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 puede mantener el "escáner" y la "salida" como campos mutables en su sistema bajo prueba. Me gusta mantener las clases lo más apátridas posible, pero eso no es una concesión muy grande si le importa a usted oa sus compañeros de trabajo / instructor.
También puede optar por poner su 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 privado del paquete.
fuente
Me las arreglé para encontrar una forma más sencilla. Sin embargo, debe usar la biblioteca externa System.rules de @Stefan Birkner
Solo tomé el ejemplo que se proporciona 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; } }
Prueba
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 todo el flujo de entrada sea nulo.
fuente
inputEntry = scanner.nextLine();
cuando se usa conSystem.in
siempre esperará al usuario (y aceptará una línea vacía como vacíaString
) ... mientras que cuando suministre líneas,systemInMock.provideLines()
esto arrojaráNoSuchElementException
cuando las líneas se agoten. Esto realmente hace que sea fácil no "distorsionar" demasiado el código de la aplicación para adaptarse a las pruebas.systemInMock.provideLines("1", "2");
2. Usando System.setIn sin bibliotecas externas:String data2 = "1 2";
System.setIn(new ByteArrayInputStream(data2.getBytes()));
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 (), es posible que desee considerar el uso de un objeto simulado.
fuente
He solucionado el problema de leer desde stdin para simular una consola ...
Mi problema era que me gustaría intentar escribir en JUnit, probar la consola para crear un objeto determinado ...
El problema es como todo lo que dices: ¿Cómo puedo escribir en la prueba Stdin de JUnit?
Luego, en la universidad, aprendo sobre 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 esperando la lectura de su nuevo InputStream, por lo que necesita crear un hilo para leer desde InputStream y desde JUnit test Thread escribir en el nuevo Stdin ... Primero tiene que escriba en Stdin porque si escribe más tarde o crea el Thread para leer desde stdin, es probable que tenga condiciones de carrera ... puede escribir en InputStream antes de leer o puede leer desde InputStream antes de escribir ...
Este es mi código, mi habilidad en inglés es mala. Espero que todos puedan entender el problema y la solución para simular la escritura en stdin de la prueba 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(); }
fuente
Me ha resultado útil crear una interfaz que defina métodos similares a java.io.Console y luego usarla para leer o escribir en System.out. La implementación real se delegará en System.console () mientras que su versión de JUnit puede ser un objeto simulado con entrada predefinida y respuestas esperadas.
Por ejemplo, construiría una MockConsole que contuviera la entrada enlatada del usuario. La implementación simulada sacaba una cadena de entrada de la lista cada vez que se llamaba a readLine. También recopilaría toda la salida escrita en una lista de respuestas. Al final de la prueba, si todo salió bien, entonces toda su entrada se habría leído y puede afirmar en la salida.
fuente