viernes, 29 de junio de 2018

Capítulo 30: Testing Patterns - TDD By Example


Pruebas hijas

Cuando escribimos un caso de prueba tan grande que no se puede continuar con su desarrollo por su dimensión habrá que abordarlo de manera que se escriba un caso de prueba más pequeño que represente parte del caso de prueba más grande. Una vez que el caso de prueba más pequeño está funcionando entonces se reintroduce el caso de prueba más grande.

El ciclo «red/green/refactor» es tan importante para el éxito del desarrollo que cuando se está en riesgo de perderlo merece la pena el esfuerzo de mantenerlo.

Cuando se escribe una prueba demasiado larga hay que tratar de aprender y analizar el porqué ha sido demasiado larga.

Por ejemplo, si en un caso de uso hay que desarrollar tres elementos (A, B y C) podría darse el caso de que desarrollarlas a la vez fuera demasiado complejo. Si se desarrollaran por separado A, B y C el caso de uso sería mucho más sencillo de implementar.

Objetos simulados o Mock objects

¿Como se prueba un objeto que depende de un recurso caro o complicado? Pues creando una versión simulada de ese recurso que contenga datos predefinidos.

Un ejemplo clásico de un recurso para simular es la base de datos. Esta requiere de un tiempo para empezar, es difícil de mantener limpia, etc.

La solución más común es no usar la base de datos real si no escribir en un objeto que actúe como la base de datos pero que esté utilizando realmente la memoria.

Otra ventaja de los objetos simulados, además del rendimiento y de la confiabilidad, es la legibilidad ya que este tendrá definidos los datos que se van a utilizar. Al utilizar una base de datos esta podría contener resultados de otras pruebas y, por ejemplo, una consulta podría devolver 14 elementos y no se sabría realmente porqué tendría que devolver 14 resultados como respuesta correcta.

Los objetos simulados añaden un riesgo ya que podrían no comportarse como lo haría el recurso real. Esto podría solucionarse si este tuviera un conjunto de pruebas para que coincida con los resultado del recurso que se está simulando cuando este esté disponible.

Self-Shunt

En los casos en los que haya que probar si un objeto se comunica correctamente con otro se podría utilizar un objeto simulado («mock object») para comprobar si la comunicación es la correcta. Otra opción es utilizar la propia clase de prueba en vez del objeto simulado o de algún «mock framework».

Por ejemplo, se quiere actualizar de forma dinámica la barra verde de la interfaz de pruebas del usuario. Si se pudiera conectar un objeto «ResultListener» al objeto «TestResult», este podría notificar al primero si la prueba falla, si ha finalizado, etc. Cada vez que se ejecuta una prueba se debe actualizar la interfaz.

La prueba podría escribirse de la siguiente manera.


El objeto que va a contar las notificaciones.


Aunque realmente, si la clase «ResultListener» se está desarrollando como un objeto simulado podemos utilizar la propia clase de la prueba para que haga la misma función.


Las pruebas que están escritas con el patrón «Self Shunt» tienden a leerse mejor. En el ejemplo anterior se puede apreciar que el valor inicial de la cuenta es 0 y después pasa a 1. ¿Cuando se ejecuta el método «startTest()»? Este debe ejecutarse cuando se ejecutan las pruebas.

Es posible que aplicando «Self Shunt» haya que crear algún tipo de interfaz para definir el comportamiento del objeto con el que se quiere comunicar.

Puede darse el caso de que la interfaz vaya añadiendo comportamiento por lo que este comportamiento debe ser implementado en aquellas clases que la utilicen, incluida la clase de pruebas. Esto puede dar lugar a multitud de implementaciones en la clase dejando la clase un poco menos legible.

Trazas de cadenas Log string

En ocasiones tendremos el problema de saber si una secuencia de mensajes se está ejecutando en el orden adecuado. Para ello se puede utilizar un «log» con las cadenas de los mensajes.

Hay un ejemplo de este tipo de estrategia durante el desarrollo de xUnit.

Las cadenas de «log» son especialmente útiles cuando se quiere implementar «Observers» y se esperan una secuencia de notificaciones en un orden determinado. En los casos en los que el orden no sea importante se puede guardar un conjunto de cadenas de manera que se compare el conjunto en la afirmación.

Crash test dummy

Los «crash test dummy» se utilizan como un objeto especial que se encarga de lanzar las excepciones que podrían darse en situaciones excepcionales de error.

Por ejemplo, si queremos probar que ocurre cuando nuestra aplicación quiere guardar un archivo y el disco duro está lleno. El enfoque no consistiría en crear muchos ficheros grandes hasta llenar el disco duro sino en simular el efecto.

El «crash test dummy» podría ser el siguiente.


Por lo que se podría escribir la prueba.


Los objecto Los «crash test dummy» son como un «Mock object» a excepción de que no se necesita simular el objeto entero. Se puede sobreescribir el método que se necesita simular.

Broken Test

Cuando uno está programando en solitario y se termina una sesión de programación se recomienda dejar la última prueba que no se ejecute. De esta manera, cuando se retome el desarrollo en la siguiente sesión hay un punto de partida obvio para empezar y es más fácil retomar el estado mental en el que se dejó la sesión previa.

Clean Check-in

Cuando se está programando en equipo y se termina una sesión, todos las pruebas del equipo deben poder ejecutarse.

Al comenzar una sesión de programación no se sabe cómo habrá cambiado el código por lo que el lugar ideal para empezar es un lugar confianza y certeza. Este lugar es aquel en el que todos las pruebas se pueden ejecutar sin errores.

No hay comentarios:

Publicar un comentario