¿Deben los test unitarios usar la base de datos?

ojoUna duda que me plantean habitualmente cuando hablo de testeo unitario en una charla o cuando trato de implantar el uso de esta técnica en un cliente es: ¿Deben los test unitarios usar la base de datos?

La respuesta ‘académica’ es NO. Pero la verdad es que en esto del software la única verdad absoluta es que las verdades absolutas son pocas… y está pregunta no escapa a esta máxima.

Con Team System y la edición para Database Professionals, el testeo unitario llego a los procedimientos almacenados, pero no es de este tipo de testeo, que evidentemente si que usa la base de datos, del que voy a hablar aquí. Este post trata sobre la conveniencia o no de que nuestros test unitarios de las capas de acceso a datos y de lógica de negocio se ejecuten con una conexión a una base de datos real establecida y con un juego de datos de prueba.

Los motivos por los que la respuesta académica es no, son claros: Es una premisa universalmente aceptada es que los test deben poderse correr en cualquier entorno, sin dependencias más que del propio framework de testeo elegido. Podéis repasar Beneficios y caraterísticas de un buen test unitario y de TDD, antes de contuniar leyendo si quereís conocer más de estas premisas. Se busca minimizar al máximo las dependencias externas de nuestros test. Debe evitarse por lo tanto, siendo puristas, el depender de la presencia de un servidor de base de datos para que nuestros test se ejecuten satisfactoriamente. Esto a menudo supone que para lograr los niveles de cobertura adecuados debemos recurrir al uso de ‘mock object’ (objetos que simulan la funcionalidad provista por la base de datos, en el caso en que nos ocupa o que simulan cualquier servicio externo requerido por el componente que testeamos). Crear los ‘mock objects’ es una tarea tediosa, que obtiene como recompensa la independencia respecto a recursos externos de nuestros test. Otra de las ventajas claras de usar ‘mock objects’ es que el rendimiento de nuestros test será alto. Al fin y al cabo los ‘mock objects’ no hacen nada de proceso.

Que nadie entienda que no doy importancia a la independencia respecto del entorno de los test. Es algo importante pero para mi no siempre es lo más importante. Lo más importante que, en mi opinión el testeo unitario aporta es la detección temprana de regresiones y de impactos no previstos en partes del software que no han sufrido cambios pero que se han visto afectadas por cambios en otras partes. Dicho esto, y en base a mi experiencia, mi recomendación suele ser, que, en sistemas guiados por datos, donde el almacenamientos de datos es una cuestión central, los test unitarios utilicen la base de datos.

Mi experiencia es que a menudo es necesario realizar refactorizaciones o cambios en la base de datos que son muy susceptibles de producir regresiones, introducir problemas de rendimiento o impactar en partes inesperadas de nuestro software. A menudo pequeños cambios en la base de datos que parecen inofensivos tienen un gran impacto. Solo si nuestros test unitarios actúan sobre la base de datos y no solamente sobre ‘mock objects’ seremos capaces de detectar de manera temprana estos problemas, con el ahorro de tiempo de desarrollo que esta detección temprana de los errores lleva aparejada.

Además, ¿qué mejor manera de probar el comportamiento de nuestra base de datos frente a bloqueos y su rendimiento que tener a varios desarrolladores corriendo a menudo test  que ejercitan el acceso a datos de manera concurrente?.

Que nuestros test unitarios ‘tiren’ de la base de datos, nos proporciona la ventaja de poder hacer cambios en nuestro esquema de datos con la confianza de que detectaremos los errores que estos cambios puedan introducir, pero también introduce una serie de problemas que tendremos que abordar:

Tenemos que garantizar que los tests en cualquier caso, se ejecuten o no de manera satisfactoria, dejarán la base de datos en el estado inicial. Nunca la ejecución de test unitarios debe cambiar el estado del sistema. Si respetamos esta máxima podremos utilizar nuestros test unitarios para detectar problemas en un sistema en producción sin temer que los datos reales sean dañados. Lograr que los test no cambien el estado de la base de datos es abordable, pero no sencillo, utilizando las posibilidades transaccionales del gestor de base de datos. Resumiendo: los test serán responsables de generar y limpiar los datos necesarios para que se ejecuten satisfactoriamente.

Otro problema que tenemos que abordar es que todos los desarrolladores deberán disponer de una versión actualizada de la base de datos de manera que puedan correr los test siempre contra la versión de la base de datos más actualizada. Esto no es difícil de conseguir en equipos pequeños y que están ubicados en un único emplazamiento, pero mucho más complejo si se trata de proyectos físicamente distribuidos.

Por último esta la cuestión del rendimiento. Que los test realmente tiren de la base de datos hace que sean más lentos que si tiran de ‘mock objects’. Una máxima del testeo unitario dice que los test deben ser rápidos en su ejecución, pues sino se dejan de correr a menudo y pierden su utilidad. Esto nos pone en la necesidad de ser más cuidadosos con el diseño de nuestros test para que su rendimiento sea adecuado.

En cualquier caso aunque el precio a pagar es alto: los test deben generar y limpiar los datos de test, tendremos que tener un servidor de bases de datos continuamente actualizado y debemos de estar atentos al rendimiento de los test, la detección temprana de los errores introducidos por cambios en el esquema de la base de datos y la comprobación temprana del rendimiento y el comportamiento frente a concurrencia de nuestra base de datos hace que el esfuerzo merezca la pena.

12 comentarios sobre “¿Deben los test unitarios usar la base de datos?”

  1. Totalmente de acuerdo Rodrigo, además con Visual Studio DB Pro, es muy fácil generar un script de inserción de datos iniciales que pueen ser de pruebas, y ejecutar ese script, antes de la ejecución de las pruebas unitarias, además se puede tener un script que deje la base de datos con datos «buenos» para ejecutar despues de la ejecución de las pruebas unitarias, con lo que todas estas tareas se hacen más sencillas.

  2. Cierto Luis, VS DB Pro facilita mucho la labor, lo malo es que no se porque capricho del destino a mí siempre me toca tener que lidiar con Oracle en los desarrollos :(. Ya me gustaría poder desarrollar solo contra SQL Server y poder aprovechar VS DB Pro a fondo…

    Menos mál que parece que en el futuro próximo VS DB Pro va a funcionar también con Oracle y DB2.

  3. Hola Rodrigo, gracias por la calidad de tus artículos.

    En algunos casos es completamente imposible utilizar Mock objects, por ejemplo cuando se utilizan modelos que siguen el patrón Active Record y se quieren probar asociaciones entre distintos objetos.

    No suelo trabajar en entornos .NET, pero en Ruby y PHP suelo usar ficheros de texto en formato yaml para la inserción de contenido en la base de datos.

    Una técnica que a mi me ha funcionado muy bien es la utilización de «migraciones» para reflejar los cambios en la base de datos. Al mismo tiempo permiten distribuir los cambios en la estructura de la base de datos a través del sistema de control de versiones.

    Limpiar el sistema tras la ejecución de los tests no me permite consultar el estado de la base de datos en el caso de que los tests fallen. Además, si el test produce una segmentación en memoria y no realiza las labores de limpieza, tienes que «limpiar el sistema a mano» y eso si que es poco «académico».

    Usando migraciones puedo encapsular en un método llamado installAndIncludeModels(‘Post’,’Comment’,’User’,’Roles’) la tarea de desinstalar las versiones definidas e instalar la más reciente. Otra ventaja de esta técnica es que los tests se ejecutan siempre con estructura de la base de datos más reciente.

    Seguiré de cerca tu blog.

  4. Bermi, gracias por compartir tus conocimientos con nosotros!!! La aproximación que útilizas es muy interesante. Ese modo de trabajar que tu ya realizas es el que facilita muchísimo VS for Database Professionals.

    Un saludo!!!

  5. Pues yo creo que el hecho de que la ejecución de los test sea rápida es también importantísimo. No me parece que tener a un desarrollador mirando la pantalla 10 minutos cada vez que quiera ejecutar los test sea lo más optimo. Y sobre todo bajo una metodología en la que se haga hincapié en la importancia de las pruebas, ya que estas deben ejecutarse casi con cada compilación.
    También estoy de acuerdo en que a veces es bastante complicado probar sin conectarse al origen de datos (sea esta una base de datos, un servicio, un conjunto de archivos, etc).
    Es por esto que hay que tener presente las pruebas desde el momento mismo del inicio de la definición de la arquitectura del sistema. Si… ya sé que esto es «muy académico», pero es a lo que hay que tender.
    Desarrollar sistemas con piezas muy pequeñas, muy simples e independientes y que puedan ser probadas; esta debe ser la premisa a tener siempre presente (personalmente tengo una regla básica que siempre intento cumplir: ningún método puede tener más de 50 líneas)
    Una camino alternativo para permitir mantener pruebas unitarias conectadas sin penalizar el resto puede ser agruparlas en dos conjuntos; las conectadas y las «mockeadas»

  6. @crisfervil: es un peaje que hay que pagar. Si tus test usan la base de datos te protegen más a coste de tardar más…
    Un solución a la tardanza en los test, que me gusta más que separalos, es usar integración continua. De esta manera solo ejecutas la porción de test que crees que han sido impactados y el proceso de integración ejecuta todos en background y te informa del resultado.

    Separa los test en los lentos y lo rápidos te llevará a no ejecutar los lentos con la frecuencia necesaria y los test que no se ejecutan con frecuencia entorpecen más que ayudan.

    Un saludo!

Responder a lfraile Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *