¿Cuál es la mejor estrategia para realizar pruebas unitarias de aplicaciones basadas en bases de datos?
Trabajo con muchas aplicaciones web que funcionan con bases de datos de diversa complejidad en el backend. Normalmente, hay una capa ORM separada de la lógica empresarial y de presentación. Esto hace que las pruebas unitarias de la lógica empresarial sean bastante sencillas; las cosas se pueden implementar en módulos discretos y cualquier dato necesario para la prueba se puede falsificar mediante la burla de objetos.
Pero probar el ORM y la base de datos en sí siempre ha estado plagado de problemas y compromisos.
A lo largo de los años, he probado algunas estrategias, ninguna de las cuales me satisfizo por completo.
Cargue una base de datos de prueba con datos conocidos. Ejecute pruebas con el ORM y confirme que regresen los datos correctos. La desventaja aquí es que su base de datos de prueba tiene que mantenerse al día con cualquier cambio de esquema en la base de datos de la aplicación y podría perder la sincronización. También se basa en datos artificiales y no puede exponer errores que ocurren debido a entradas estúpidas del usuario. Finalmente, si la base de datos de prueba es pequeña, no revelará ineficiencias como la falta de un índice. (Está bien, esto último no es realmente para lo que deberían usarse las pruebas unitarias, pero no hace daño).
Cargue una copia de la base de datos de producción y pruébela. El problema aquí es que es posible que no tenga idea de lo que hay en la base de datos de producción en un momento dado; Es posible que sea necesario reescribir sus pruebas si los datos cambian con el tiempo.
Algunas personas han señalado que ambas estrategias se basan en datos específicos y que una prueba unitaria debería probar sólo la funcionalidad. Con ese fin, he visto sugerido:
- Utilice un servidor de base de datos simulado y verifique solo que el ORM esté enviando las consultas correctas en respuesta a una llamada a un método determinado.
¿Qué estrategias ha utilizado para probar aplicaciones basadas en bases de datos, si las ha utilizado? ¿Qué ha funcionado mejor para ti?
De hecho, utilicé su primer enfoque con bastante éxito, pero de maneras ligeramente diferentes que creo que resolverían algunos de sus problemas:
Mantenga todo el esquema y los scripts para crearlo en el control de código fuente para que cualquiera pueda crear el esquema de base de datos actual después de realizar el check-out. Además, mantenga los datos de muestra en archivos de datos que se cargan como parte del proceso de compilación. A medida que descubra datos que causan errores, agréguelos a sus datos de muestra para verificar que los errores no vuelvan a surgir.
Utilice un servidor de integración continua para crear el esquema de la base de datos, cargar los datos de muestra y ejecutar pruebas. Así es como mantenemos nuestra base de datos de prueba sincronizada (reconstruyéndola en cada ejecución de prueba). Aunque esto requiere que el servidor CI tenga acceso y propiedad de su propia instancia de base de datos dedicada, digo que tener nuestro esquema de base de datos creado 3 veces al día ha ayudado enormemente a encontrar errores que probablemente no se habrían encontrado hasta justo antes de la entrega (si no más tarde). ). No puedo decir que reconstruya el esquema antes de cada confirmación. ¿Alguien? Con este enfoque no tendrás que hacerlo (bueno, tal vez deberíamos hacerlo, pero no es gran cosa si alguien lo olvida).
Para mi grupo, la entrada del usuario se realiza en el nivel de la aplicación (no en la base de datos), por lo que se prueba mediante pruebas unitarias estándar.
Cargando copia de la base de datos de producción:
este fue el enfoque que utilicé en mi último trabajo. Fue un gran dolor causado por un par de problemas:
- La copia quedaría desactualizada con respecto a la versión de producción.
- Se realizarían cambios en el esquema de la copia y no se propagarían a los sistemas de producción. En este punto tendríamos esquemas divergentes. No es divertido.
Servidor de base de datos simulado:
también hacemos esto en mi trabajo actual. Después de cada confirmación, ejecutamos pruebas unitarias contra el código de la aplicación que tiene inyectados accesores simulados a la base de datos. Luego, tres veces al día ejecutamos la compilación completa de la base de datos descrita anteriormente. Definitivamente recomiendo ambos enfoques.
Siempre estoy ejecutando pruebas en una base de datos en memoria (HSQLDB o Derby) por estos motivos:
- Le hace pensar qué datos conservar en su base de datos de prueba y por qué. Simplemente transportar su base de datos de producción a un sistema de prueba se traduce en "¡No tengo idea de lo que estoy haciendo ni por qué y si algo se rompe, no fui yo!". ;)
- Garantiza que la base de datos se pueda recrear con poco esfuerzo en un lugar nuevo (por ejemplo, cuando necesitamos replicar un error de producción).
- Ayuda enormemente con la calidad de los archivos DDL.
La base de datos en memoria se carga con datos nuevos una vez que comienzan las pruebas y después de la mayoría de las pruebas, invoco ROLLBACK para mantenerla estable. ¡ SIEMPRE mantenga estables los datos en la base de datos de prueba! Si los datos cambian todo el tiempo, no puede realizar la prueba.
Los datos se cargan desde SQL, una base de datos de plantilla o un volcado/copia de seguridad. Prefiero los volcados si están en un formato legible porque puedo ponerlos en VCS. Si eso no funciona, uso un archivo CSV o XML. Si tengo que cargar enormes cantidades de datos… no lo hago. Nunca tendrás que cargar enormes cantidades de datos :) No para pruebas unitarias. Las pruebas de rendimiento son otra cuestión y se aplican reglas diferentes.
Incluso si existen herramientas que le permiten burlarse de su base de datos de una forma u otra (por ejemplo, jOOQMockConnection
, que se puede ver en esta respuesta ; descargo de responsabilidad, trabajo para el proveedor de jOOQ), recomendaría no burlarse de bases de datos más grandes con complejos consultas.
Incluso si solo desea probar la integración de su ORM, tenga en cuenta que un ORM emite una serie muy compleja de consultas a su base de datos, que pueden variar en
- sintaxis
- complejidad
- orden (!)
Burlarse de todo eso para producir datos ficticios sensatos es bastante difícil, a menos que realmente estés construyendo una pequeña base de datos dentro de tu simulacro, que interprete las declaraciones SQL transmitidas. Dicho esto, utilice una base de datos de pruebas de integración conocida que pueda restablecer fácilmente con datos conocidos, con los que pueda ejecutar sus pruebas de integración.
He estado haciendo esta pregunta durante mucho tiempo, pero creo que no existe una solución mágica para eso.
Lo que hago actualmente es burlarme de los objetos DAO y mantener una representación en memoria de una buena colección de objetos que representan casos interesantes de datos que podrían vivir en la base de datos.
El principal problema que veo con ese enfoque es que estás cubriendo solo el código que interactúa con tu capa DAO, pero nunca probando el DAO en sí, y en mi experiencia veo que también ocurren muchos errores en esa capa. También mantengo algunas pruebas unitarias que se ejecutan en la base de datos (para usar TDD o pruebas rápidas localmente), pero esas pruebas nunca se ejecutan en mi servidor de integración continua, ya que no mantenemos una base de datos para ese propósito y yo Creo que las pruebas que se ejecutan en el servidor CI deberían ser independientes.
Otro enfoque que encuentro muy interesante, pero que no siempre vale la pena porque requiere un poco de tiempo, es crear el mismo esquema que se utiliza para la producción en una base de datos integrada que simplemente se ejecuta dentro de las pruebas unitarias.
Aunque no hay duda de que este enfoque mejora su cobertura, existen algunos inconvenientes, ya que debe estar lo más cerca posible de ANSI SQL para que funcione tanto con su DBMS actual como con el reemplazo integrado.
No importa lo que crea que es más relevante para su código, existen algunos proyectos que pueden hacerlo más fácil, como DbUnit .