¿Son malas las pruebas de integración (base de datos)?

116

Algunas personas sostienen que las pruebas de integración son todas malas y malas - todo debe ser probado por unidades, lo que significa que tienes que simular las dependencias; una opción que, por diversas razones, no siempre me gusta.

Encuentro que, en algunos casos, una prueba de unidad simplemente no prueba nada.

Tomemos como ejemplo la siguiente implementación de repositorio (trivial, ingenua) (en PHP):

class ProductRepository
{
    private $db;

    public function __construct(ConnectionInterface $db) {
        $this->db = $db;
    }

    public function findByKeyword($keyword) {
        // this might have a query builder, keyword processing, etc. - this is
        // a totally naive example just to illustrate the DB dependency, mkay?

        return $this->db->fetch("SELECT * FROM products p"
            . " WHERE p.name LIKE :keyword", ['keyword' => $keyword]);
    }
}

Supongamos que quiero demostrar en una prueba que este repositorio puede encontrar productos que coincidan con varias palabras clave.

A falta de pruebas de integración con un objeto de conexión real, ¿cómo puedo saber que esto en realidad está generando consultas reales y que esas consultas realmente hacen lo que creo que hacen?

Si tengo que burlarme del objeto de conexión en una prueba de unidad, solo puedo probar cosas como "genera la consulta esperada", pero eso no significa que realmente vaya a trabajar . ... es decir, tal vez esté generando la consulta que esperaba, pero tal vez esa consulta no haga lo que creo que hace.

En otras palabras, me parece que una prueba que hace afirmaciones sobre la consulta generada, esencialmente no tiene valor, porque está probando cómo se implementó el método findByKeyword() , pero eso no prueba que en realidad funciona .

Este problema no se limita a los repositorios o la integración de la base de datos, parece aplicarse en muchos casos, donde hacer afirmaciones sobre el uso de un simulacro (prueba doble) solo prueba cómo se implementan las cosas, no si se implementan. voy a trabajar realmente.

¿Cómo lidias con situaciones como estas?

¿Las pruebas de integración son realmente "malas" en un caso como este?

Entiendo que es mejor probar una cosa, y también entiendo por qué las pruebas de integración conducen a innumerables rutas de código, todas las cuales no se pueden probar, pero en el caso de un servicio (como un repositorio) cuyo El único propósito es interactuar con otro componente, ¿cómo puede realmente probar algo sin pruebas de integración?

    
pregunta mindplay.dk 02.11.2015 - 17:58

11 respuestas

123

Su compañero de trabajo tiene razón en que todo lo que se puede probar en una unidad debe probarse por unidad, y tiene razón en que las pruebas de unidad lo llevarán tan lejos como sea posible, especialmente cuando escriba envoltorios simples sobre servicios externos complejos. / p>

Una forma común de pensar acerca de las pruebas es como una pirámide de pruebas . Es un concepto frecuentemente relacionado con Agile, y muchos lo han escrito, incluyendo Martin Fowler (quien lo atribuye a Mike Cohn en Exitoso con Agile ), Alistair Scott , y el Blog de pruebas de Google .

        /\                           --------------
       /  \        UI / End-to-End    \          /
      /----\                           \--------/
     /      \     Integration/System    \      /
    /--------\                           \----/
   /          \          Unit             \  /
  --------------                           \/
  Pyramid (good)                   Ice cream cone (bad)

La noción es que las pruebas unitarias de ejecución rápida y resistente son la base del proceso de prueba: debería haber pruebas unitarias más enfocadas que las pruebas de integración / sistema, y más pruebas de integración / sistema que las pruebas de extremo a extremo. A medida que se acerca a la cima, las pruebas tienden a tomar más tiempo / recursos para ejecutarse, tienden a estar sujetos a más fragilidad y descamación, y son menos específicos al identificar qué sistema o archivo está roto ; naturalmente, es preferible evitar ser "muy pesado".

Hasta ese momento, las pruebas de integración no son malas , pero una gran confianza en ellas puede indicar que no ha diseñado sus componentes individuales para que sean fáciles de probar. Recuerde, el objetivo aquí es probar que su unidad está funcionando a su especificación mientras involucra un mínimo de otros sistemas rompibles : es posible que desee probar una base de datos en memoria (que cuento como una unidad) prueba fácil de probar doble junto con simulacros) para pruebas de casos extremos, por ejemplo, y luego escriba un par de pruebas de integración con el motor de base de datos real para establecer que los casos principales funcionan cuando se ensambla el sistema.

Como nota al margen, mencionaste que los simulacros que escribes simplemente prueban cómo se implementa algo, no si funciona . Eso es una especie de antipatrón: una prueba que es un reflejo perfecto de su implementación no es realmente probar nada en absoluto. En su lugar, pruebe que cada clase o método se comporte de acuerdo con su propia especificación , en cualquier nivel de abstracción o realismo que requiera.

    
respondido por el Jeff Bowman 02.11.2015 - 20:39
87
  

Uno de mis compañeros de trabajo sostiene que las pruebas de integración son todas malas e incorrectas: todo debe ser probado por unidades,

Es un poco como decir que los antibióticos son malos: todo se debe curar con vitaminas.

Las pruebas unitarias no pueden detectar todo: solo prueban cómo funciona un componente en un entorno controlado . Las pruebas de integración verifican que todo funciona juntos , lo cual es más difícil de hacer pero al final es más significativo.

Un proceso de prueba bueno y completo utiliza ambos tipos de pruebas: pruebas unitarias para verificar las reglas de negocios y otras cosas que pueden probarse de manera independiente, y pruebas de integración para asegurarse de que todo funcione en conjunto.

  

A falta de pruebas de integración con un objeto de conexión real, ¿cómo puedo saber que esto en realidad está generando consultas reales y que esas consultas realmente hacen lo que creo que hacen?

Usted podría probarlo en la unidad a nivel de base de datos . Ejecute la consulta con varios parámetros y vea si obtiene los resultados que espera. Por supuesto, significa copiar / pegar cualquier cambio nuevamente en el código "verdadero". pero le permite probar la consulta independientemente de cualquier otra dependencia.

    
respondido por el D Stanley 02.11.2015 - 18:03
15

Las pruebas unitarias no detectan todos los defectos. Pero son más baratos de configurar y (re) ejecutar en comparación con otros tipos de pruebas. Las pruebas unitarias se justifican por la combinación de valor moderado y costo bajo a moderado.

Aquí hay una tabla que muestra las tasas de detección de defectos para diferentes tipos de pruebas.

fuente: p.470 en Code Complete 2 por McConnell

    
respondido por el Nick Alexeev 03.11.2015 - 05:23
12

No, no son malos. Con suerte, uno debería tener pruebas de unidad e integración. Se utilizan y se ejecutan en diferentes etapas del ciclo de desarrollo.

Pruebas de unidad

Las pruebas unitarias deben ejecutarse en el servidor de compilación y localmente, después de que se haya compilado el código. Si alguna de las pruebas unitarias falla, se debe fallar la compilación o no confirmar la actualización del código hasta que se solucionen las pruebas. La razón por la que queremos que las pruebas unitarias estén aisladas es que queremos que el servidor de compilación pueda ejecutar todas las pruebas sin todas las dependencias. Entonces podríamos ejecutar la compilación sin todas las dependencias complejas requeridas y realizar muchas pruebas que se ejecutan muy rápido.

Entonces, para una base de datos, uno debería tener algo como:

IRespository

List<Product> GetProducts<String Size, String Color);

Ahora, la implementación real de IRepository irá a la base de datos para obtener los productos, pero para las pruebas unitarias, se puede simular una IRepository con una falsa para ejecutar todas las pruebas según sea necesario sin una base de datos precisa, ya que podemos simular todo tipo de listas de productos que se devuelven desde la instancia simulada y prueban cualquier lógica empresarial con los datos simulados.

Pruebas de integración

Las pruebas de integración suelen ser pruebas de cruce de límites. Queremos ejecutar estas pruebas en el servidor de implementación (el entorno real), en la zona de pruebas o incluso localmente (apuntado a la zona de pruebas). No se ejecutan en el servidor de compilación. Después de que el software se haya implementado en el entorno, normalmente se ejecutará como una actividad posterior a la implementación. Se pueden automatizar a través de las utilidades de línea de comandos. Por ejemplo, podemos ejecutar nUnit desde la línea de comandos si categorizamos todas las pruebas de integración que queremos invocar. Estos realmente llaman al repositorio real con la llamada de base de datos real. Este tipo de pruebas ayudan con:

  • Preparación para la estabilidad de la salud del medio ambiente
  • Probando lo real

Estas pruebas a veces son más difíciles de ejecutar, ya que es posible que también tengamos que configurarlas y / o demolerlas. Considere agregar un producto. Probablemente deseamos agregar el producto, consultarlo para ver si se agregó, y luego, una vez que hayamos terminado, eliminarlo. No queremos agregar cientos o miles de productos de "integración", por lo que se requiere una configuración adicional.

Las pruebas de integración pueden resultar bastante valiosas para validar un entorno y asegurarse de que funcione de verdad.

Uno debe tener ambos.

  • Ejecuta las pruebas unitarias para cada compilación.
  • Ejecute las pruebas de integración para cada implementación.
respondido por el Jon Raynor 02.11.2015 - 22:13
10

Las pruebas de integración de la base de datos no son malas. Aún más, son necesarios.

Probablemente tienes la aplicación dividida en capas, y es algo bueno. Puede probar cada capa de forma aislada burlándose de las capas vecinas, y eso también es bueno. Pero no importa cuántas capas de abstracción cree, en algún punto tiene que haber una capa que haga el trabajo sucio, en realidad hablar con la base de datos. A menos que lo pruebes, no lo prueba. Si prueba la capa n simulando la capa n-1 está evaluando el supuesto de que la capa n funciona a condición de que La capa n-1 funciona. Para que esto funcione, debes demostrar de alguna manera que la capa 0 funciona.

Aunque, en teoría, podría unificar la base de datos de prueba, analizando e interpretando el SQL generado, es mucho más fácil y más confiable crear una base de datos de prueba sobre la marcha y hablar con él.

Conclusión

¿Cuál es la confianza obtenida de las pruebas de unidad de su Repositorio abstracto , Objeto etéreo-Relacional-Mapeador , Registro activo genérico , teórico Persistencia en las capas, cuando al final el SQL generado contiene un error de sintaxis?

    
respondido por el el.pescado 02.11.2015 - 23:14
6

Necesitas ambos.

En su ejemplo, si estaba probando una base de datos en una determinada condición, cuando se ejecuta el método findByKeyword obtiene los datos que espera, esta es una prueba de integración excelente.

En cualquier otro código que esté utilizando el método findByKeyword , usted quiere controlar lo que se alimenta en la prueba, de modo que pueda devolver el nulo o las palabras correctas para su prueba o lo que sea, entonces se burla de la dependencia de la base de datos para sepa exactamente qué recibirá su prueba (y perderá la sobrecarga de conectarse a una base de datos y asegurarse de que la información sea correcta)

    
respondido por el Froome 02.11.2015 - 18:07
6

El autor del artículo del blog al que hace referencia es Preocupado principalmente por la complejidad potencial que puede surgir de las pruebas integradas (aunque está escrito de una manera muy crítica y categórica). Sin embargo, las pruebas integradas no son necesariamente malas, y algunas son en realidad más útiles que las pruebas de unidad pura. Realmente depende del contexto de su aplicación y de lo que intenta probar.

Muchas aplicaciones hoy simplemente no funcionarán si su servidor de base de datos falla. Al menos, piénselo en el contexto de la función que está intentando probar.

Por un lado, si lo que intenta probar no depende de la base de datos, o se puede hacer que no dependa de él, escriba su prueba de tal manera que ni siquiera lo intente para utilizar la base de datos (solo proporcione datos simulados según sea necesario).  Por ejemplo, si está tratando de probar alguna lógica de autenticación al servir una página web (por ejemplo), probablemente sea una buena cosa separarla de la base de datos (suponiendo que no confíe en la base de datos para la autenticación, o que puedes burlarte de él razonablemente fácilmente).

Por otra parte, si es una función que se basa directamente en su base de datos y no funcionaría en un entorno real en caso de que la base de datos no esté disponible, entonces se burla de lo que hace el DB en su código de cliente (es decir, la base de datos). la capa que usa ese DB) no necesariamente tiene sentido.

Por ejemplo, si sabe que su aplicación se basará en una base de datos (y posiblemente en un sistema de base de datos específico), burlarse del comportamiento de la base de datos por el bien de ella a menudo será una pérdida de tiempo. Los motores de base de datos (especialmente RDBMS) son sistemas complejos. Algunas líneas de SQL pueden realizar una gran cantidad de trabajo, lo que sería difícil de simular (de hecho, si su consulta SQL tiene unas pocas líneas, es probable que necesite muchas más líneas de Java / PHP / C # / Python). código para producir el mismo resultado internamente): no tiene sentido duplicar la lógica que ya implementó en la base de datos, y verificar que el código de prueba se convierta en un problema en sí mismo.

No necesariamente trataría esto como un problema de prueba de unidad v.s. prueba integrada , sino más bien mirar el alcance de lo que se está probando. Los problemas generales de las pruebas de unidad e integración continúan: necesita un conjunto de datos de prueba y casos de prueba razonablemente realistas, pero también es lo suficientemente pequeño para que las pruebas se ejecuten rápidamente.

El momento para restablecer la base de datos y volver a llenar con datos de prueba es un aspecto a considerar; por lo general, lo evaluarías en función del tiempo que lleva escribir ese código simulado (que también deberías mantener, eventualmente).

Otro punto a considerar es el grado de dependencia que su aplicación tiene con la base de datos.

  • Si su aplicación simplemente sigue un modelo CRUD, donde tiene una capa de abstracción que le permite intercambiar cualquier RDBMS por medio de un simple ajuste de configuración, es probable que pueda trabajar con un sistema simulado con bastante facilidad. (posiblemente borrando la línea entre la unidad y las pruebas integradas usando un RDBMS en memoria).
  • Si su aplicación usa una lógica más compleja, algo que sería específico de uno de SQL Server, MySQL, PostgreSQL (por ejemplo), entonces generalmente tendría más sentido tener una prueba que use ese sistema específico.
respondido por el Bruno 03.11.2015 - 15:04
1

Tienes razón al pensar que una prueba de unidad de este tipo está incompleta. Lo incompleto está en la interfaz de la base de datos que se está burlando. Tal expectativa o aseveración de ingenuos simulacros son incompletas.

Para completarlo, tendrías que ahorrar suficiente tiempo y recursos para escribir o integrar un motor de reglas SQL que garantice que la declaración SQL sea emitida por el sujeto bajo prueba, daría como resultado operaciones.

Sin embargo, la alternativa / acompañante a menudo burlada a menudo olvidada y un tanto costosa es "virtualización" .

¿Puede activar una instancia de base de datos temporal, en memoria pero "real" para probar una sola función? si ahí tienes una mejor prueba, la que verifica los datos reales guardados y recuperados.

Ahora, se podría decir, convirtió una prueba de unidad en una prueba de integración. Hay diferentes vistas sobre dónde trazar la línea para clasificar entre pruebas de unidad y pruebas de integración. En mi humilde opinión, "unidad" es una definición arbitraria y debe ajustarse a sus necesidades.

    
respondido por el S.D. 03.11.2015 - 21:15
0

Unit Tests y Integration Tests son ortogonales entre sí. Ofrecen una vista diferente de la aplicación que estás creando. Por lo general, desea que ambos . Pero el punto en el tiempo difiere, cuando se desea qué tipo de pruebas.

Lo más a menudo que desee Unit Tests . Las pruebas unitarias se centran en una pequeña parte del código que se está probando: lo que exactamente se llama unit se deja al lector. Pero el propósito es simple: obtener comentarios rápidos sobre cuándo y dónde se rompió su código . Dicho esto, debe quedar claro, que las llamadas a una base de datos real es un nono .

Por otro lado, hay cosas que solo pueden ser unidades probadas en condiciones difíciles sin una base de datos. Quizás haya una condición de carrera en su código y una llamada a una base de datos arroje una violación de unique constraint que solo se podría lanzar si realmente usa su sistema. Pero ese tipo de pruebas son caras que no puede (y no quiere) ejecutarlas tan a menudo como unit tests .

    
respondido por el Thomas Junk 27.04.2016 - 20:01
0

En el mundo .Net, tengo la costumbre de crear un proyecto de prueba y crear pruebas como un método de codificación / depuración / prueba de ida y vuelta menos la interfaz de usuario. Esta es una manera eficiente de desarrollarme. No estaba tan interesado en ejecutar todas las pruebas para cada compilación (porque ralentiza mi flujo de trabajo de desarrollo), pero entiendo la utilidad de esto para un equipo más grande. Sin embargo, podría establecer una regla que indique que antes de confirmar el código, todas las pruebas deben ejecutarse y aprobarse (si las pruebas se demoran más porque la base de datos está siendo afectada).

Al burlarse de la capa de acceso a datos (DAO) y no llegar realmente a la base de datos, no solo no me permite codificar como me gusta y me he acostumbrado, sino que pierde una gran parte del código real. Si realmente no está probando la capa de acceso a datos y la base de datos y solo está fingiendo, y luego pasa mucho tiempo burlándose de las cosas, no logro comprender la utilidad de este enfoque para probar realmente mi código. Estoy probando una pieza pequeña en lugar de una más grande con una prueba. Entiendo que mi enfoque podría estar más en la línea de una prueba de integración, pero parece que la prueba de unidad con el simulacro es una pérdida de tiempo redundante si en realidad solo escribe la prueba de integración una vez y primero. También es una buena forma de desarrollar y depurar.

De hecho, desde hace un tiempo he estado al tanto de TDD y Behavior Driven Design (BDD) y he pensado en formas de usarlo, pero es difícil agregar pruebas unitarias de forma retroactiva. Quizás me equivoque, pero escribir una prueba que cubra más código de extremo a extremo con la base de datos incluida, parece una prueba mucho más completa y de mayor prioridad para escribir que cubre más código y es una manera más eficiente de escribir pruebas.

De hecho, creo que algo como Behavior Driven Design (BDD) que intenta probar de extremo a extremo con un lenguaje específico del dominio (DSL) debería ser el camino a seguir. Tenemos SpecFlow en el mundo .Net, pero comenzó como código abierto con Cucumber.

enlace

Realmente no estoy impresionado con la verdadera utilidad de la prueba que escribí burlándose de la capa de acceso a datos y no golpeando la base de datos. El objeto devuelto no llegó a la base de datos y no se llenó con datos. Era un objeto completamente vacío que tuve que burlarme de una manera poco natural. Simplemente creo que es una pérdida de tiempo.

Según Stack Overflow, el simulacro se utiliza cuando los objetos reales no se pueden incorporar a la prueba de la unidad.

enlace

"El simulacro se usa principalmente en pruebas unitarias. Un objeto bajo prueba puede tener dependencias de otros objetos (complejos). Para aislar el comportamiento del objeto que desea probar, reemplace los otros objetos por simulacros que simulan el comportamiento del objeto. objetos reales. Esto es útil si los objetos reales no son prácticos para incorporarlos a la prueba unitaria ".

Mi argumento es que si estoy codificando algo de extremo a extremo (de la interfaz de usuario web a la capa empresarial a la capa de acceso a datos a la base de datos, ida y vuelta), antes de verificar algo como desarrollador, voy a probar esta ida y vuelta fluir. Si elimino la interfaz de usuario, depuro y pruebo este flujo a partir de una prueba, estoy probando todo lo que no sea la interfaz de usuario y devuelvo exactamente lo que la interfaz de usuario espera. Todo lo que me queda es enviar a la UI lo que quiere.

Tengo una prueba más completa que forma parte de mi flujo de trabajo de desarrollo natural. Para mí, esa debería ser la prueba de máxima prioridad que cubre probar la especificación del usuario real de extremo a extremo tanto como sea posible. Si nunca creo ninguna otra prueba más granular, al menos tengo esta una prueba más completa que prueba que mi funcionalidad deseada funciona.

Un cofundador de Stack Exchange no está convencido de los beneficios de tener una cobertura de prueba unitaria del 100%. Yo tampoco soy. Tomaría una "prueba de integración" más completa que llega a la base de datos sobre el mantenimiento de un montón de simulacros de base de datos cualquier día.

enlace

    
respondido por el user3198764 09.03.2017 - 19:29
-1

Las dependencias externas deben simularse porque no puede controlarlas (pueden pasar durante la fase de prueba de integración pero fallar en la producción). Las unidades pueden fallar, las conexiones de la base de datos pueden fallar por varias razones, puede haber problemas en la red, etc. Tener pruebas de integración no le da ninguna confianza adicional porque son todos los problemas que podrían ocurrir en el tiempo de ejecución.

Con las pruebas unitarias verdaderas, estás realizando pruebas dentro de los límites de la caja de arena y debería quedar claro. Si un desarrollador escribió una consulta SQL que falló en QA / PROD, significa que ni siquiera la probaron una vez antes de esa fecha.

    
respondido por el vidalsasoon 02.11.2015 - 20:26

Lea otras preguntas en las etiquetas