¿Cómo crear pruebas de integración gratuitas escalables y sin efectos secundarios?

14

En mi proyecto actual, me cuesta mucho encontrar una buena solución para crear pruebas de integración escalables que no tengan efectos secundarios. Una pequeña aclaración sobre la propiedad libre de efectos secundarios: se trata principalmente de la base de datos; no debe haber ningún cambio en la base de datos después de que se completen las pruebas (se debe conservar el estado). Tal vez la escalabilidad y la conservación del estado no se unan, pero realmente quiero buscar una mejor solución.

Aquí hay una prueba de integración típica (estas pruebas tocan la capa de la base de datos):

public class OrderTests {

    List<Order> ordersToDelete = new ArrayList<Order>(); 

    public testOrderCreation() {
        Order order = new Order();
        assertTrue(order.save());
        orderToDelete.add(order);
    }

    public testOrderComparison() {
        Order order = new Order();
        Order order2 = new Order();
        assertFalse(order.isEqual(order2);
        orderToDelete.add(order);
        orderToDelete.add(order2);
    }
    // More tests

    public teardown() {
         for(Order order : ordersToDelete)
             order.delete();
    }
}

Como uno podría imaginar, este enfoque produce pruebas que son extremadamente lentas. Y, cuando se aplica a todas las pruebas de integración, toma alrededor de 5 segundos probar solo una pequeña parte del sistema. Puedo imaginar que este número aumenta cuando se aumenta la cobertura.

¿Cuál sería otro enfoque para escribir tales pruebas? Una alternativa que se me ocurre es tener un tipo de variables globales (dentro de una clase) y todos los métodos de prueba comparten esta variable. Como resultado, solo unos pocos pedidos se crean & eliminado resultando en pruebas más rápidas. Sin embargo, creo que esto introduce un problema mayor; las pruebas ya no están aisladas y cada vez son más difíciles de entender & analizarlos.

Puede ser que las pruebas de integración no estén destinadas a ejecutarse tan a menudo como las pruebas unitarias; por lo tanto, un bajo rendimiento podría ser aceptable para aquellos. En cualquier caso, sería bueno saber si a alguien se le ocurrieron alternativas para mejorar la escalabilidad.

    
pregunta Guven 21.10.2011 - 02:07

5 respuestas

6

Considere el uso de Hypersonic, u otro DB en memoria para pruebas de unidad. No solo las pruebas se ejecutan más rápido, sino que los efectos secundarios no son relevantes. (Revertir las transacciones después de cada prueba también hace posible ejecutar muchas pruebas en la misma instancia)

Esto también lo forzará a crear maquetas de datos, lo cual es bueno para la OMI, ya que significa que algo que ocurre en la base de datos de producción no puede comenzar a fallar sus pruebas inexplicablemente, Y le da un punto de partida para lo que " La instalación de la base de datos limpia "se verá como, lo que ayuda si necesita implementar repentinamente una nueva instancia de la aplicación sin conexión a su sitio de producción existente.

Sí, utilizo este método yo mismo, y sí, fue un PITA el que se configuró la PRIMERA vez, pero es más que pagado en la vida del proyecto que estoy a punto de completar. También hay una serie de herramientas que ayudan con las maquetas de datos.

    
respondido por el SplinterReality 21.10.2011 - 03:48
3

Este es el eterno problema al que todos se enfrentan al escribir pruebas de integración.

La solución ideal, especialmente si está probando en producción, es abrir una transacción en la configuración y revertirla en desmontaje. Creo que esto debería satisfacer sus necesidades.

Cuando eso no sea posible, por ejemplo, cuando está probando la aplicación desde la capa del cliente, otra solución es usar una base de datos en una máquina virtual y tomar una instantánea en la configuración y volver a ella en desmontaje ( no toma tanto tiempo como podría esperarse).

    
respondido por el pdr 21.10.2011 - 03:14
3

Las pruebas de integración siempre deben ejecutarse contra la configuración de producción. En su caso, significa que debe tener la misma base de datos y el mismo servidor de aplicaciones. Por supuesto, por el bien del rendimiento, puede decidir utilizar una base de datos en memoria.

Sin embargo, lo que nunca debe hacer es ampliar el alcance de su transacción. Algunas personas sugirieron tomar el control de la transacción y revertirla después de las pruebas. Cuando haga esto, todas las entidades (suponiendo que esté utilizando la JPA) permanecerán vinculadas al contexto de persistencia durante toda la ejecución de la prueba. Esto puede dar como resultado algunos errores muy desagradables que son muy difíciles de encontrar .

En cambio, debe borrar la base de datos manualmente después de cada prueba en una biblioteca como JPAUnit o similar a < a href="http://bripkens.de/blog/2010/12/clear-database-java-jdbc/"> este enfoque (borre todas las tablas usando JDBC).

A partir de sus problemas de rendimiento, no debe ejecutar las pruebas de integración en cada compilación. Deja que tu servidor de integración continua haga esto. Si usa Maven, puede disfrutar del complemento failafe que le permite separar sus pruebas en Pruebas unitarias y de integración.

Además, no debes burlarte de nada. Recuerde que está realizando pruebas de integración, es decir, está probando el comportamiento dentro del entorno de ejecución en tiempo de ejecución.

    
respondido por el BenR 21.10.2011 - 21:11
2

Sobre la escalabilidad

He tenido este problema algunas veces antes, porque las pruebas de integración demoraban demasiado en ejecutarse y no eran prácticos para que un solo desarrollador se ejecutara continuamente en un círculo de cambios de retroalimentación ajustado. Algunas estrategias para hacer frente a esto son:

  • Escriba menos pruebas de integración : si tiene una buena cobertura con las pruebas unitarias en el sistema, las pruebas de integración deberían centrarse más en (ejem) los problemas de integración, en cómo trabajan juntos los diferentes componentes. Son más similares a las pruebas de humo, que solo intentan ver si el sistema todavía funciona como un todo. Así que, idealmente, las pruebas unitarias cubren la mayoría de las partes funcionales de la aplicación, y las pruebas de integración simplemente verifican cómo interactúan esas partes entre sí. No necesita una amplia cobertura de rutas lógicas aquí, solo algunas rutas críticas a través del sistema.
  • Ejecutar solo un subconjunto de las pruebas a la vez : como un solo desarrollador que trabaja en algunas funciones, normalmente tiene una idea de qué pruebas son necesarias para cubrir esa funcionalidad. Ejecute solo las pruebas que tengan sentido para cubrir sus cambios. Los marcos como JUnit te permiten agrupar los elementos en un categoría . Cree las agrupaciones que permitan la mejor cobertura para cada función que tenga. Por supuesto, aún desea ejecutar todas las pruebas de integración en algún momento, tal vez antes de comprometerse con el sistema de control de origen y, ciertamente, en el servidor de integración continua.
  • Optimice el sistema : las pruebas de integración combinadas con algunas pruebas de rendimiento pueden proporcionarle la información necesaria para ajustar partes lentas del sistema, de modo que las pruebas se ejecuten más rápido más adelante. Esto podría ayudar a descubrir los índices necesarios en la base de datos, las interfaces de chat entre subsistemas u otros cuellos de botella de rendimiento que necesitan ser resueltos.
  • Ejecute sus pruebas en paralelo : si tiene una buena agrupación de pruebas ortogonales, puede intentar ejecutarlas en parallel . Sin embargo, esté atento al requisito ortogonal que mencioné, usted no quiere que sus pruebas se interpongan en los demás pies.

Intenta combinar estas técnicas para obtener un mayor efecto.

    
respondido por el Jordão 21.10.2011 - 17:48
2

Con fines de prueba, hemos utilizado una implementación basada en archivos de una base de datos SQLite (solo copie un recurso). Esto se hizo para que también pudiéramos probar las migraciones de esquemas. Por lo que sé, los cambios de esquema no son transaccionales, por lo que no se revertirán después de que una transacción sea objeto de referencia. Además, el hecho de no tener que depender del soporte de transacciones para la configuración de prueba permite probar el comportamiento de las transacciones desde su aplicación correctamente.

    
respondido por el Carlo Kuip 21.10.2011 - 21:19

Lea otras preguntas en las etiquetas