Los Mocks estrictos son una herramienta de diseño de software
26-06-2023
Este artículo surge a raíz de un debate sobre las soluciones generadas en la kata Mouse Events en las sesiones de formación de Lean Mind. Para resolver esta kata es necesario usar dobles de test, pues no hay exposición del estado interno del artefacto Mouse. Los dobles de test que se ajustaban al caso son, en la terminología expuesta por Gerad Meszaros, el doble Spy y el Mock escricto. Hubo argumentos a favor y en contra de usar uno u otro. Se defendía que el Spy permitía más cadencia de desarrollo y flexibilidad, mientras que el Mock aseguraba más confiabilidad de que todas las precondiciones necesarias/esperadas ocurrirían. Sobre esto último, se expuso que hacía que los tests fueran más frágiles ante cambios futuros, pues fija cómo deberán ser las interacciones entre objetos, de modo que, ante posibles cambios de estos, los tests se volverían un lastre. Sin embargo, hubo un argumento expuesto que puso sobre la mesa una dimensión, que al menos por mi parte, no había meditado. Este fue algo similar a:
“A lo mejor ese cambio futuro no debería ser sobre ninguno de los objetos que ya ha sido probado con Mocks estrictos, si los tests fallan puede ser un indicativo que esa nueva funcionalidad no encaja ahí, si se han establecido expectativas desde un punto de vista de diseño, este no debería ser modificado, sino extendido”.
¿Qué hay detrás del argumento? ¡Veámoslo!
El orígen
El argumento fue expuesto por Carlos Blé a raíz de una entrada de blogpost de J.B. Rainsberger (”JMock v. Mockito, but not to the death”). En este, el autor expone dos ideas fundamentales:
- Los Spy son adecuados para testear código legado porque tolera la complejidad accidental presente ya que se pone bajo observación una parte atómica de la interacción entre artefactos, obviando la secuencia necesaria que debería darse. De esta manera, permite descubrirla mediante exploración o triangulación. Por lo tanto, en un proyecto Green-field no tiene sentido utilizarlos, ya que no hay impedimentos para crear el código necesario y garantizar la secuencia mencionada.
- Los Mock Objects estrictos son adecuados en proyectos Green-field ya que no hay impedimentos para declarar en los tests cómo deben ser las interacciones entre objetos. Al definir y controlar la secuencia de interacción entre objetos, ayuda a cumplir el principio SOLID abierto-cerrado, y al mismo tiempo, reducir la introducción de complejidad accidental.
Estos argumentos son contundentes pero echaba en falta más fundamentos para que tuviesen el peso suficiente para respaldar la tesis: “ Los Mocks estrictos son una herramienta de diseño”.
Fundamentos
El software como una red de interacción de objetos
En el diseño de software, con programación orientada a objetos (POO), tiene realmente más importancia cómo los objetos (que componen un software/sistema) se comunican, qué cómo están diseñados estos individualmente porque el comportamiento de un software surge/emerge de la interacción entre estos. Por tanto, adoptar este enfoque, orientado al comportamiento del sistema, implica que desarrollo se debe centrar en cómo se construye la red de comunicación de objetos. El beneficio principal es el desarrollo de objetos que son fácilmente conectables con otros, es decir, que siguen los patrones de comunicación comunes y las dependencias son explícitas. Estos patrones se basan en trabajar con los objetos bajo relaciones de Roles, Responsabilidades, y Colaboraciones. Para entender esto te expongo lo que se entiende con los anteriores términos:
- Una responsabilidad es una obligación/compromiso de realizar una acción o conocer información
- Un Rol es un conjunto de responsabilidades relacionadas
- Un objeto es una implementación de uno o más roles
- Una colaboración es una interacción entre objetos o roles
De esta manera, el diseño se centraría en desarrollar objetos definiéndoles un/os rol/es con unas responsabilidades y unos colaboradores necesarios para cumplir con estas. Para este tipo de diseño se suelen aplicar Tarjetas CRC (Candidates, Resposabilities and collaborators).
Por otro lado, al desarrollar software con POO, deberíamos evitar exponer el estado interno de los objetos con el fin de:
- Garantizar la integridad y control de estados
- Evitar la declaración de clases anémicas
- Evitar acoplamiento y aumentar la cohesión
Esto se puede conseguir evitando preguntar por información a los objetos y con esta generar un resultado, sino diciéndoles que tienen que hacer para generar el resultado por paso de un mensaje (invocación de un método). Esto, a un nivel de diseño más alto, debería traducirse en “un objeto debería describir lo que quiere de sus colaboradores, actuando estos con un rol determinado”. En definitiva, se consigue hacer explícita qué interacción se ha de producir, más en cómo se produce. Ahora bien, ¿Cómo testeamos esto?
Testing de la colaboración de objetos
Para realizar testing sobre artefactos es necesario un punto de observación. El caso sobre un único objeto es simple: se observa que ante unos inputs dados (sobre un punto de entrada accesible), se produzca un resultado esperado (el cual también es accesible), como si de una caja negra se tratara. Sin embargo, cuando se trata de testar la colaboración entre objetos, esta puede no tener ni la entrada de información ni la salida expuesta, un punto de observación accesible. Además, puede ser necesaria que ocurran varias entradas y salidas de información entre los objetos para poder verificar un comportamiento.
No deberíamos exponer estados internos de los objetos para tener un punto de observación tal y cómo se comentaba antes, de modo que una estrategia para lidiar con esto es el uso de dobles de test. Si atendemos, a todos los posibles dobles que expone Gerad Meszaros, los más adecuados son los mocks estrictos y los fake objects. El primero permite verificar que las interacciones se producen correctamente para que surja el comportamiento esperado del sistema y el segundo es una implementación simplificada de una real que permite exponer puntos de observación. El problema de usar el segundo es que obliga a mantener un artefacto no productivo para poder seguir manteniendo operativos los test ya creados más soportar otros futuros. En cambio, los mock objects solo tienen una misión: asegurar que el colaborador y el artefacto bajo test interactúen correctamente bajo un lenguaje declarativo de expectativas, el cual se abstrae de cómo es la lógica ejecutable por el paso de mensaje entre objetos. En el caso de que el mock object deba devolver valores, estos también se declaran si se cumple la secuencia.
Otros dobles como los Spy o los Stub permiten evidenciar comportamientos del sistema solo de forma parcial y pueden servir como soporte cuando representan objetos que no son el foco principal o no tienen una interacción. Aunque cabría preguntarse si se hace esto, ¿No será que ese colaborador no está bien planteado? Como la discusión puede quedarse en los grises, podemos dejarlo solo en la pregunta
Conclusiones
Los Mocks estrictos son una herramienta de diseño porque permiten establecer, antes de desarrollar cada objeto individualmente, cómo van a interaccionar, es decir, permiten diseñar el comportamiento del sistema, que es el foco principal de la POO en el desarrollo de software.
Agradecimientos
A Lean Mind, a todo el equipo, de dar la posibilidad a sesiones donde surgen debates que plantan la semilla de la curiosidad.