2009-05-18 54 views
21

Quiero simular un ResultSet. Seriamente. Estoy refaccionando una gran pieza complicada de código que está analizando datos de ResultSet, y quiero que mi código se comporte de manera idéntica. Entonces, necesito escribir una prueba unitaria para la pieza que se está refactorizando para poder probar esto.Manera fácil de llenar ResultSet con datos

Tras Google me ocurrió con 2 ideas:

  1. Uso EasyMock, escribir looooong secuencia de burla. Solución MUY MALA: datos iniciales difíciles de agregar, datos difíciles de cambiar, grandes promesas de depuración de pruebas.
  2. Utilice Apache Derby o HSQLDB para crear una base de datos en memoria, llenarla desde un archivo o matriz de cadenas, consultar con InMemoryDBUtils.query (sql) mágico. Luego usa ese ResultSet. Desafortunadamente, no encontré ningún InMemoryDBUtils mágico para escribir la prueba rápidamente :-). El artículo de IBM "Prueba unitaria aislada de persistencia con Derby" parece estar bien sobre lo que necesito, aunque ...

El segundo enfoque parece algo más fácil y mucho más compatible.

¿Qué aconsejaría para crear un simulacro? (a pesar de los médicos, por supuesto :-)? ¿Me falta una ceja alguna bala de plata? Posiblemente, ¿DBUnit es la herramienta para esto?

Respuesta

11

DBUnit no presenta un conjunto de resultados, que yo sepa, aunque le ayudará a llenar su base de datos de memoria.

Yo diría que un marco de burla es el enfoque equivocado en este punto. Burlarse se trata de probar el comportamiento y la interacción, no solo devolver datos, por lo que es probable que se interponga en su camino.

En su lugar, implementaría una interfaz de conjunto de resultados o crearía un proxy dinámico de una interfaz de conjunto de resultados para una clase que implemente los métodos que le interesan sin tener que implementar todo el conjunto de resultados. Es probable que encontrar el mantenimiento de una clase sea tan fácil como mantener una base de datos en memoria (siempre que el conjunto de datos bajo prueba sea consistente), y probablemente más fácil de depurar.

Puede hacer una copia de seguridad de esa clase con DBUnit, donde toma una instantánea de su conjunto de resultados con dbunit, y lee dbunit durante la prueba desde xml y hace que su conjunto de resultados ficticio lea los datos de las clases de dbunit. Este sería un enfoque razonable si los datos fueran levemente complejos.

Me gustaría ir a la base de datos en memoria si las clases estaban tan acopladas que necesitan leer los datos que se modificaron como parte de la misma prueba. Incluso entonces, consideraría usar una copia de la base de datos real hasta que haya logrado separar esa dependencia.

Un simple método de generación de proxy:

private static class SimpleInvocationHandler implements InvocationHandler { 
    private Object invokee; 

    public SimpleInvocationHandler(Object invokee) { 
     this.invokee = invokee; 
    } 

    public Object invoke(Object proxy, Method method, Object[] args) 
      throws Throwable { 
     method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes()); 
     if (!method.isAccessible()) { 
      method.setAccessible(true); 
     } 
     try { 
      return method.invoke(invokee, args); 
     } catch (InvocationTargetException e) { 
      throw e.getTargetException(); 
     } 
    } 
} 

public static <T> T generateProxy(Object realObject, Class... interfaces) { 
    return (T) Proxy.newProxyInstance(realObject.getClass().getClassLoader(), interfaces, new SimpleInvocationHandler(realObject)); 
} 
+0

Hola, Yishai, gracias por tus comentarios. Me burlaré de unos 100 registros por cada una de las 10 o más pruebas, con un registro que consta de 10 campos. Esto, AFAIU, me deja con DBUnit para almacenar los datos en el archivo y la implementación de ResultSet personalizada. Gracias de nuevo por responder. – DiaWorD

+0

De nada. Yo diría que si cada una de las 10 pruebas requiere datos diferentes, entonces sí, este sería un enfoque razonable. Con DBUnit puede tomar un ResultSet y escribirlo en XML, por lo que solo hace referencia a eso en la prueba. – Yishai

2

Si corresponde, podría tomar el conjunto de resultados que tiene ahora de su fuente de datos real, serializarlo y guardar el archivo. Entonces podrías deserializar ese conjunto de resultados para cada una de tus pruebas unitarias, y deberías estar listo para continuar.

+0

Hola GWLlosa, y gracias por su comentario también. Creo que DBUnit me ayudará a almacenar datos en un archivo. – DiaWorD

1

Mientras usted no está llamando a la mayor parte de los métodos ResultSet, probablemente sólo tiene que cargar un archivo de texto delimitado en una matriz de dos dimensiones, y poner en práctica los métodos efectivamente que necesitaba, dejando el resto para lanzar un UnsupportedOperationException (que es la implementación predeterminada para métodos apagados en mi IDE).

36

he tenido éxito con la clase MockResultSet desde aquí: http://mockrunner.sourceforge.net/. Le permite crear una clase que implementa la interfaz ResultSet y le permite establecer los valores para cada columna y fila.

Si sus métodos funcionan con ResultSets de tamaño razonable, debería poder crear pruebas que devuelvan los valores que necesita con bastante facilidad.

Aquí está un ejemplo sencillo:

MockResultSet rs = new MockResultSet("myMock"); 

rs.addColumn("columnA", new Integer[]{1}); 
rs.addColumn("columnB", new String[]{"Column B Value"}); 
rs.addColumn("columnC", new Double[]{2}); 

// make sure to move the cursor to the first row 
try 
{ 
    rs.next(); 
} 
catch (SQLException sqle) 
{ 
    fail("unable to move resultSet"); 
} 

// process the result set 
MyObject obj = processor.processResultSet(rs); 

// run your tests using the ResultSet like you normally would 
assertEquals(1, obj.getColumnAValue()); 
assertEquals("Column B Value", obj.getColumnBValue()); 
assertEquals(2.0d, obj.getColumnCValue()); 
+0

Hola mjd79, Y gracias por su respuesta. El problema en mi caso es que definitivamente tendré unos 100 registros para cada caso de prueba y cerca de 10 casos de prueba :-) Cada registro tiene 10 campos, incluyendo fechas, números y cadenas. Debido a eso, preferiría almacenar datos falsos en un archivo, preferiblemente en forma INSERT INTO, que se puede tomar directamente de casi todos los clientes de DB. MockRunner se ve bastante bien, pero lo guardaré para otro caso. Gracias de nuevo por su tiempo. – DiaWorD

+0

mockrunner fue un gran recurso para simular un conjunto de resultados simple para una prueba de trabajo por lotes. ¡Gracias un montón! – EdgeCaseBerg

+0

Esta debería ser la respuesta aceptada. – JonyD

5

Mockrunner puede cargar un archivo CSV o XML y crear una MockResultSet automáticamente. También puede simular Connection y Statement, por lo que todo su material JDBC simplemente funciona, sin siquiera agregar un controlador JDBC a su classpath.

5

He escrito algo para este mismo caso. Puede simular el conjunto de resultados usando Mockito. También puede recorrer las filas simuladas del conjunto de resultados burlándose del conjunto de resultados.next() con este fragmento de código.

// two dimensional array mocking the rows of database. 
String[][] result = { { "column1", "column2" }, { "column1", "column2" } }; 

@InjectMocks 
@Spy 
private TestableClass testableClass; 

@Mock 
private Connection connection; 

@Mock 
private Statement statement; 

@Mock 
private ResultSet resultSet; 

@BeforeTest 
public void beforeTest() { 
    MockitoAnnotations.initMocks(this); 
} 

@BeforeMethod 
public void beforeMethod() throws SQLException { 
    doAnswer(new Answer<Connection>() { 
     public Connection answer(InvocationOnMock invocation) 
       throws Throwable { 
      return connection; 

     } 
    }).when(testableClass).getConnection(); 

    when(connection.createStatement()).thenReturn(statement); 
    when(statement.executeQuery(anyString())).thenReturn(resultSet); 
    final AtomicInteger idx = new AtomicInteger(0); 
    final MockRow row = new MockRow(); 

    doAnswer(new Answer<Boolean>() { 

     @Override 
     public Boolean answer(InvocationOnMock invocation) throws Throwable { 
      int index = idx.getAndIncrement(); 
      if (result.length > index) { 
       String[] current = result[index]; 
       row.setCurrentRowData(current); 
       return true; 
      } else 
       return false; 

     } 

     ; 
    }).when(resultSet).next(); 

    doAnswer(new Answer<String>() { 

     @Override 
     public String answer(InvocationOnMock invocation) throws Throwable { 
      Object[] args = invocation.getArguments(); 
      int idx = ((Integer) args[0]).intValue(); 
      return row.getColumn(idx); 
     } 

     ; 
    }).when(resultSet).getString(anyInt()); 
} 

static class MockRow { 
    String[] rowData; 

    public void setCurrentRowData(String[] rowData) { 
     this.rowData = rowData; 
    } 

    public String getColumn(int idx) { 
     return rowData[idx - 1]; 
    } 
} 
Cuestiones relacionadas