2009-10-30 22 views
40

Tengo un programa de línea de comandos de Java. Me gustaría crear el caso de prueba JUnit para poder simular System.in. Porque cuando mi programa se ejecuta, entrará en el ciclo while y esperará la entrada de los usuarios. ¿Cómo simulo eso en JUnit?JUnit: ¿Cómo simular las pruebas de System.in?

Gracias

Respuesta

49

Es técnicamente posible cambiar System.in, pero, en general, sería más sólido no llamarlo directamente en el código, sino agregar una capa de indirección para que la fuente de entrada se controle desde un punto de la aplicación. Exactamente cómo lo hace es un detalle de implementación: las sugerencias de inyección de dependencia son buenas, pero no necesariamente es necesario introducir marcos de terceros; por ejemplo, podría pasar un contexto de E/S del código de llamada.

cómo cambiar System.in:

String data = "Hello, World!\r\n"; 
InputStream stdin = System.in; 
try { 
    System.setIn(new ByteArrayInputStream(data.getBytes())); 
    Scanner scanner = new Scanner(System.in); 
    System.out.println(scanner.nextLine()); 
} finally { 
    System.setIn(stdin); 
} 
1

Se puede crear una costumbre InputStream y adjuntarlo a la clase System

class FakeInputStream extends InputStream { 

    public int read() { 
     return -1; 
    } 
} 

Y luego utilizarlo con su Scanner

System.in = new FakeInputStream();

Antes:

InputStream in = System.in; 
... 
Scanner scanner = new Scanner(in); 

Después:

InputStream in = new FakeInputStream(); 
... 
Scanner scanner = new Scanner(in); 

Aunque creo que deberías mejor probar cómo su clase debe trabajar con los datos leídos del flujo de entrada y no es realmente la forma en que se lee a partir de ahí.

+0

Desde una perspectiva de TDD, esto evita el diseño que la prueba es "conducción" o tratar de indicar. Sin embargo, el OP no especificó TDD, y desde una perspectiva de prueba posterior, es algo muy razonable de hacer: aprovechar el sistema global. – Yishai

+0

No puede simplemente hacer System.in = xxx como System.in es final. Puedes usar System.setIn, pero asegúrate de volver al valor predeterminado en el desglose. Además, no necesita pasar su propio InputStream, ByteArrayInputStream hará el trabajo muy bien. –

+0

Oohh yeap, me confundí. Lo que intenté decir fue ... bueno, editaré mi entrada :) – OscarRyz

4

Intente refactorizar su código para usar dependency injection. En lugar de tener su método que usa System.in directamente, haga que el método acepte un InputStream como argumento. Luego, en su prueba junit, podrá pasar una implementación de prueba InputStream en lugar de System.in.

7

Hay algunas maneras de abordar esto. La forma más completa es pasar un InputStream mientras se ejecuta la clase bajo prueba, que es un InputStream falso que pasa datos simulados a su clase. Se puede ver en un marco de inyección de dependencias (como Google Guice) si necesita hacer esto mucho en su código, pero la forma más sencilla es:

public class MyClass { 
    private InputStream systemIn; 

    public MyClass() { 
     this(System.in); 
    } 

    public MyClass(InputStream in) { 
     systemIn = in; 
    } 
} 

bajo prueba que podríamos llamar el constructor que toma la entrada corriente. Incluso puedes hacer que ese paquete constructor sea privado y poner la prueba en el mismo paquete, de modo que otro código generalmente no considere usarlo.

+3

+1. Estoy contigo en esto. Yo iría un paso más allá: 'InputData' como un contenedor de alto nivel alrededor de' InputStream' en las pruebas unitarias, debería preocuparse más por lo que hace su clase, y no realmente por la integración. – OscarRyz

+0

En lugar de crear la variable 'systemIn' puede usar el método' System.setIn (in) '. Entonces puede llamar 'System.in' normalmente pero con la versión actualizada. – LaraChicharo

3

Usted puede escribir una prueba clara de la interfaz de línea de comandos usando la regla de la biblioteca System RulesTextFromStandardInputStream.

public void MyTest { 
    @Rule 
    public final TextFromStandardInputStream systemInMock 
    = emptyStandardInputStream(); 

    @Test 
    public void readTextFromStandardInputStream() { 
    systemInMock.provideLines("foo"); 
    Scanner scanner = new Scanner(System.in); 
    assertEquals("foo", scanner.nextLine()); 
    } 
} 

Descripción completa: Soy el autor de esa biblioteca.

+0

Solo traté de usar tu biblioteca, lo cual me pareció muy bueno ... pero luego traté de usarlo con Rule public MockitoRule rule = MockitoJUnit.rule() ;, y al parecer, por lo que intenté, los dos no pudieron combinarse ... Así que no pude combinarlo con inyecciones de burlas ... –

+0

Ese no debería ser el caso. ¿Podría crear un problema para la biblioteca de Reglas del sistema y proporcionar su ejemplo de error? –

+0

Gracias ... ahora sé que debe funcionar. Lo investigaré y crearé un problema, si corresponde. Parecía ser el 'TextFromStandardInputStream' con el que tuve un problema, pero el' SystemOutRule' funcionaba bien. –

1

El problema con BufferedReader.readLine() es que se trata de un método de bloqueo que espera la entrada del usuario.Me parece que no desea particularmente simular eso (es decir, desea que las pruebas sean rápidas). Pero en un contexto de prueba continuamente devuelve null a alta velocidad durante la prueba, lo cual es molesto.

Para un purista puedes hacer el getInputLine debajo del paquete privado, y burlarlo: fácil de pelar.

String getInputLine() throws Exception { 
    return br.readLine(); 
} 

... tendría que asegurarse de que tenía una forma de detener (normalmente) un bucle de interacción del usuario con la aplicación. También tendría que lidiar con el hecho de que sus "líneas de entrada" siempre serían las mismas hasta que de alguna manera haya cambiado el doReturn de su simulacro: apenas típico de la entrada del usuario.

para un no-purista que quiere hacer la vida más fácil para ellos mismos (y producir pruebas legibles) se puede poner todo esto más adelante en su código de aplicación:

private Deque<String> inputLinesDeque; 

void setInputLines(List<String> inputLines) { 
    inputLinesDeque = new ArrayDeque<String>(inputLines); 
} 

private String getInputLine() throws Exception { 
    if (inputLinesDeque == null) { 
     // ... i.e. normal case, during app run: this is then a blocking method 
     return br.readLine(); 
    } 
    String nextLine = null; 
    try { 
     nextLine = inputLinesDeque.pop(); 
    } catch (NoSuchElementException e) { 
     // when the Deque runs dry the line returned is a "poison pill", 
     // signalling to the caller method that the input is finished 
     return "q"; 
    } 

    return nextLine; 
} 

... en su prueba que podría luego ir así:

consoleHandler.setInputLines(Arrays.asList(new String[]{ "first input line", "second input line" })); 

antes de desencadenar el método en esta clase "ConsoleHandler" que necesita líneas de entrada.

0

tal como este (no probado):

InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in)); 

in.write("text".getBytes("utf-8")); 

System.setIn(save_in); 

más partes:

//PrintStream save_out=System.out;final ByteArrayOutputStream out = new ByteArrayOutputStream();System.setOut(new PrintStream(out)); 

InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in)); 

//start something that reads stdin probably in a new thread 
// Thread thread=new Thread(new Runnable() { 
//  @Override 
//  public void run() { 
//   CoursesApiApp.main(new String[]{});     
//  } 
// }); 
// thread.start(); 


//maybe wait or read the output 
// for(int limit=0; limit<60 && not_ready ; limit++) 
// { 
//  try { 
//   Thread.sleep(100); 
//  } catch (InterruptedException e) { 
//   e.printStackTrace(); 
//  } 
// } 


in.write("text".getBytes("utf-8")); 

System.setIn(save_in); 

//System.setOut(save_out); 
Cuestiones relacionadas