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

14

En mi proyecto actual, estoy teniendo dificultades para 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 debería haber ningún cambio en la base de datos después de que se completen las pruebas (el estado debe conservarse). Tal vez la escalabilidad y la preservación del estado no se unen, pero realmente quiero impulsar 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 porción del sistema. Me imagino que este número aumentará cuando aumente 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 y eliminan; resultando en pruebas más rápidas. Sin embargo, creo que esto presenta un problema mayor; las pruebas ya no están aisladas y se hace cada vez más difícil de entender y analizar.

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

Guven
fuente

Respuestas:

6

Considere el uso de Hypersonic u otro DB en memoria para pruebas unitarias. Las pruebas no solo se ejecutarán 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 obligará a crear maquetas de datos, lo cual es algo bueno de la OMI, ya que significa que algo que ocurre en la base de datos de producción no puede comenzar a fallar inexplicablemente en sus pruebas, Y le da un punto de partida para lo que una "instalación de base de datos limpia "se vería así, lo que ayuda si necesita implementar de repente una nueva instancia de la aplicación sin conexión a su sitio de producción existente.

Sí, yo mismo uso este método, y sí, fue una PITA configurar la PRIMERA vez, pero está 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.

SplinterReality
fuente
Suena como una gran idea. Me gusta especialmente el aspecto de aislamiento completo; comenzando con una nueva instalación de la base de datos. Parece que tomará un poco de esfuerzo, pero una vez configurado, será muy beneficioso. Gracias.
Guven
3

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

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

Donde 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 tómese el tiempo que pueda esperar).

pdr
fuente
No puedo creer que no haya pensado en la reversión de la transacción. Usaré esa idea como una solución rápida a la solución, pero en última instancia, el DB en memoria suena muy prometedor.
Guven
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 servidor de aplicaciones. Por supuesto, en aras del rendimiento, puede optar por utilizar un DB 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 el JPA) permanecerán unidas al contexto de persistencia durante la ejecución de la prueba. Esto puede dar lugar a algunos errores muy desagradables que son muy difíciles de encontrar .

En su lugar, debe borrar la base de datos manualmente después de cada prueba a través de una biblioteca como JPAUnit o similar a este enfoque (borrar todas las tablas usando JDBC).

Debido a sus problemas de rendimiento, no debe ejecutar las pruebas de integración en cada compilación. Deje que su servidor de integración continua haga esto. Si usa Maven, puede disfrutar del complemento a prueba de fallos que le permite separar sus pruebas en pruebas unitarias y de integración.

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

BenR
fuente
Buena respuesta. Un punto: puede ser aceptable burlarse de algunas dependencias externas (por ejemplo, envío de correo electrónico). Pero debe burlarse configurando un servidor / servicio simulado completo, no burlándose de él en el código Java.
sleske
Sleske, estoy de acuerdo contigo. Especialmente cuando está utilizando JPA, EJB, JMS o está implementando contra otra especificación. Puede intercambiar el servidor de aplicaciones, el proveedor de persistencia o la base de datos. Por ejemplo, es posible que desee utilizar Glassfish y HSQLDB integrados para simplemente configurar y mejorar la velocidad (por supuesto, puede elegir otra implementación certificada).
BenR
2

Sobre escalabilidad

He tenido este problema algunas veces antes, que las pruebas de integración estaban tardando demasiado en ejecutarse y no eran prácticas para que un solo desarrollador se ejecute continuamente en un estricto ciclo de retroalimentación de cambios. 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 los problemas de integración (ejem), en cómo los diferentes componentes trabajan juntos. Son más similares a las pruebas de humo, que solo intentan ver si el sistema aún funciona como un todo. Entonces, idealmente, sus pruebas unitarias cubren la mayoría de las partes funcionales de la aplicación, y las pruebas de integración solo verifican cómo esas partes interactúan entre sí. No necesita una amplia cobertura de rutas lógicas aquí, solo algunas rutas críticas a través del sistema.
  • Ejecute solo un subconjunto de las pruebas a la vez : como desarrollador único que trabaja en alguna funcionalidad, normalmente tiene una idea de qué pruebas son necesarias para cubrir esa funcionalidad. Ejecute solo aquellas pruebas que tengan sentido para cubrir sus cambios. Los marcos como JUnit le permiten agrupar dispositivos en una categoría . Cree las agrupaciones que permitan la mejor cobertura para cada característica 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 código fuente y ciertamente en el servidor de integración continua.
  • Optimice el sistema : las pruebas de integración junto 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 conversación entre subsistemas u otros cuellos de botella de rendimiento que necesitan abordarse.
  • Ejecute sus pruebas en paralelo : si tiene una buena agrupación de pruebas ortogonales, puede intentar ejecutarlas en paralelo . Sin embargo, esté atento al requisito ortogonal que mencioné, no desea que sus pruebas se interpongan entre sí.

Intente combinar estas técnicas para un mayor efecto.

Jordão
fuente
1
Muy buenas pautas; escribir menos pruebas de integración es sin duda el mejor consejo. Además, ejecutarlos en paralelo sería una muy buena alternativa; una "prueba" perfecta para verificar si obtuve mis pruebas directamente en términos de aislamiento.
Guven
2

Para 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 esquema. Por lo que sé, los cambios de esquema no son transaccionales, por lo que no se revertirán después de abrogar una transacción. Además, 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.

Carlo Kuip
fuente