Trabajo con muchas aplicaciones web que funcionan con bases de datos de diversa complejidad en el back-end. Por lo general, hay una capa ORM separada de la lógica empresarial y de presentación. Esto hace que las pruebas unitarias de la lógica comercial sean bastante sencillas; las cosas se pueden implementar en módulos discretos y cualquier información necesaria 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.
Con los años, he intentado algunas estrategias, ninguna de las cuales me satisfizo por completo.
Cargue una base de datos de prueba con datos conocidos. Ejecute pruebas contra el ORM y confirme que vuelven 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 no estar sincronizado. También se basa en datos artificiales, y no puede exponer errores que ocurren debido a la entrada estúpida del usuario. Finalmente, si la base de datos de prueba es pequeña, no revelará ineficiencias como un índice faltante. (Bien, ese último no es realmente para qué se deberían usar las pruebas unitarias, pero no duele).
Cargue una copia de la base de datos de producción y pruebe con eso. 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 una prueba unitaria debería probar solo la funcionalidad. Con ese fin, he visto sugerido:
- Use un servidor de base de datos simulado y verifique solo que el ORM esté enviando las consultas correctas en respuesta a una llamada de método dada.
¿Qué estrategias ha utilizado para probar aplicaciones basadas en bases de datos, si las hay? ¿Qué ha funcionado mejor para ti?
fuente
Respuestas:
De hecho, he utilizado su primer enfoque con bastante éxito, pero de una manera ligeramente diferente que creo que resolvería algunos de sus problemas:
Mantenga todo el esquema y las secuencias de comandos para crearlo en el control de origen para que cualquiera pueda crear el esquema de la base de datos actual después de una comprobación. Además, mantenga datos de muestra en archivos de datos que se cargan por 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 aparecer.
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 sincronizada nuestra base de datos de prueba (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 db construido 3 veces al día ha ayudado dramáticamente 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 reconstruyo el esquema antes de cada confirmación. ¿Alguien? Con este enfoque no tendrá que hacerlo (bueno, tal vez deberíamos, pero no es gran cosa si alguien se olvida).
Para mi grupo, la entrada del usuario se realiza a nivel de aplicación (no db), por lo que esto se prueba a través de pruebas unitarias estándar.
Carga de la copia de la base de datos de producción:
este fue el enfoque que utilicé en mi último trabajo. Fue una gran causa de un par de problemas:
Servidor de base de datos burlón:
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 inyectores db simulados inyectados. Luego, tres veces al día, ejecutamos la compilación db completa descrita anteriormente. Definitivamente recomiendo ambos enfoques.
fuente
Siempre estoy ejecutando pruebas contra un DB en memoria (HSQLDB o Derby) por estos motivos:
El DB 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 mantenerlo estable. ¡SIEMPRE mantenga estables los datos en la base de datos de prueba! Si los datos cambian todo el tiempo, no puede probar.
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 tiene que cargar enormes cantidades de datos :) No para pruebas unitarias. Las pruebas de rendimiento son otro problema y se aplican diferentes reglas.
fuente
He estado haciendo esta pregunta durante mucho tiempo, pero creo que no hay una bala de plata para eso.
Lo que actualmente hago 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 solo cubre el código que interactúa con su capa DAO, pero nunca prueba 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 (por el uso de TDD o pruebas rápidas localmente), pero esas pruebas nunca se ejecutan en mi servidor de integración continua, ya que no tenemos una base de datos para ese propósito y piensa que las pruebas que se ejecutan en el servidor CI deben ser independientes.
Otro enfoque que encuentro muy interesante, pero que no siempre vale la pena, ya que lleva un poco de tiempo, es crear el mismo esquema que usa para la producción en una base de datos integrada que solo 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 incorporado.
No importa lo que considere más relevante para su código, existen algunos proyectos que pueden facilitarlo, como DbUnit .
fuente
Incluso si hay herramientas que le permiten burlas de su base de datos de un modo u otro (por ejemplo jOOQ 's
MockConnection
, que se puede ver en esta respuesta - descargo de responsabilidad, trabajo para el proveedor de jOOQ), aconsejaría no para burlarse de las bases de datos más grandes con complejo consultasIncluso 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 puede variar en
Es bastante difícil burlarse de todo eso para producir datos ficticios sensibles, a menos que realmente esté construyendo una pequeña base de datos dentro de su simulación, que interpreta las declaraciones SQL transmitidas. Dicho esto, use una base de datos de prueba de integración conocida que pueda restablecer fácilmente con datos conocidos, contra los cuales puede ejecutar sus pruebas de integración.
fuente
Yo uso el primero (ejecutar el código contra una base de datos de prueba). El único problema sustantivo que veo surgir con este enfoque es la posibilidad de que los esquemas se desincronicen, lo que trato manteniendo un número de versión en mi base de datos y haciendo todos los cambios de esquema a través de un script que aplica los cambios para cada incremento de versión.
También realizo primero todos los cambios (incluido el esquema de la base de datos) en mi entorno de prueba, por lo que termina siendo al revés: después de que pasan todas las pruebas, aplique las actualizaciones del esquema al host de producción. También mantengo un par de bases de datos de prueba versus aplicación separadas en mi sistema de desarrollo para poder verificar allí que la actualización de db funciona correctamente antes de tocar las cajas de producción reales.
fuente
Estoy usando el primer enfoque, pero un poco diferente que permite abordar los problemas que mencionó.
Todo lo que se necesita para ejecutar pruebas para DAO está en el control de origen. Incluye esquema y scripts para crear la base de datos (docker es muy bueno para esto). Si se puede usar el DB incrustado, lo uso para la velocidad.
La diferencia importante con los otros enfoques descritos es que los datos que se requieren para la prueba no se cargan desde scripts SQL o archivos XML. Todo (excepto algunos datos del diccionario que son efectivamente constantes) es creado por la aplicación usando funciones / clases de utilidad.
El objetivo principal es hacer que los datos sean utilizados por la prueba.
Básicamente significa que estas utilidades permiten especificar declarativamente solo cosas esenciales para la prueba en la prueba misma y omitir cosas irrelevantes.
Para dar una idea de lo que significa en la práctica, considere la prueba para algunos DAO que funcionan con
Comment
s toPost
s escritos porAuthors
. Para probar las operaciones CRUD para tal DAO, se deben crear algunos datos en la base de datos. La prueba se vería así:Esto tiene varias ventajas sobre los scripts SQL o los archivos XML con datos de prueba:
Rollback vs Commit
Me parece más conveniente que las pruebas se confirmen cuando se ejecutan. En primer lugar, algunos efectos (por ejemplo
DEFERRED CONSTRAINTS
) no se pueden comprobar si nunca se realiza la confirmación. En segundo lugar, cuando falla una prueba, los datos se pueden examinar en la base de datos, ya que no se revierte la reversión.De hecho, esto tiene el inconveniente de que la prueba puede producir datos rotos y esto conducirá a fallas en otras pruebas. Para lidiar con esto trato de aislar las pruebas. En el ejemplo anterior, cada prueba puede crear nuevas
Author
y todas las demás entidades se crean relacionadas con ella, por lo que las colisiones son raras. Para tratar con los invariantes restantes que pueden romperse potencialmente pero no pueden expresarse como una restricción de nivel de DB, utilizo algunas comprobaciones programáticas para condiciones erróneas que pueden ejecutarse después de cada prueba individual (y se ejecutan en CI pero generalmente se desconectan localmente para el rendimiento razones).fuente
PostBuilder.post()
. Genera algunos valores para todos los atributos obligatorios de la publicación. Esto no es necesario en el código de producción.Para un proyecto basado en JDBC (directa o indirectamente, por ejemplo, JPA, EJB, ...) puede simular no toda la base de datos (en tal caso, sería mejor usar una base de datos de prueba en un RDBMS real), sino solo una maqueta a nivel JDBC .
La ventaja es la abstracción que viene de esa manera, ya que los datos JDBC (conjunto de resultados, recuento de actualizaciones, advertencia, ...) son los mismos, sea cual sea el backend: su prod db, un db de prueba o solo algunos datos de maquetas proporcionados para cada prueba caso.
Con la conexión JDBC simulada para cada caso, no es necesario administrar la prueba db (limpieza, solo una prueba a la vez, recargar los accesorios, ...). Cada conexión de maqueta está aislada y no hay necesidad de limpiarla. Solo se proporcionan accesorios mínimos requeridos en cada caso de prueba para simular el intercambio JDBC, lo que ayuda a evitar la complejidad de administrar una base de datos de prueba completa.
Acolyte es mi marco que incluye un controlador JDBC y una utilidad para este tipo de maquetas: http://acolyte.eu.org .
fuente