Test Driven Development

Test Driven Development

December 23, 2021

Introducción

TDD es una técnica de diseño de software que propone empezar por los test y que el diseño vaya surgiendo de ellos. Pregonada por Kent Beck, Robert Martin, Martin Fowler, etc. aunque con distintas escuelas entre ellos pero todos coinciden en los principales lineamientos. TDD se encuentra enmarcado dentro de las técnicas sugeridas por Extreme Programming en su apartado de pruebas automatizadas.

Entre sus ventajas se encuentra una mejor cobertura de los test automatizados, un mejor diseño del software y menor presencia de bugs, entre otras. Si bien TDD no nos garantiza un buen diseño, el hecho de hacer las cosas testeables nos lleva a desacoplarlas mejor y eso ya es una ganancia. Dentro de sus ventajas más importantes está la cuestión metodológica: TDD nos propone abordar la complejidad en pasos pequeños (un Test) y esto es mucho más eficiente y satisfactorio como metodología de desarrollo. También podemos destacar entre sus beneficios, que al abordar las reglas de negocio de a una, las porciones de código en las que estamos trabajado reducen su volumen. De esta manera estamos mucho más cerca de un error cuando sucede, esto nos permite saber casi con exactitud dónde falló el test y por qué. De esta manera casi cae en desuso la técnica debugging. Es por este motivo que podemos abordar la complejidad de manera granular, entonces nos enfrentamos con los problemas de hacer funcionar nuestro código y de que tenga buena calidad por separado.

"Make it work, make it right, make it fast" - Kent Beck

A continuación expondré algunas características de la idea clásica y algunos puntos comunes entre las diferentes escuelas, a modo introductorio. En artículos futuros intentaremos profundizar en las diferencias que existen, sus ventajas y desventajas y las formas que existen para aplicarlas.

Primero, ¿qué es un Test?

Llamamos Test, en este contexto, a una porción de código con la que probamos una parte de nuestra aplicación. Sobre la morfología y técnicas de construcción de un Test hablaremos más adelante, pero es importante por ahora entender que es código ejecutable de carácter lógico (Fail or Success). Dentro de un test vamos a poner a rodar nuestro código productivo, simulando escenarios y asertando sobre resultados esperados.

Si te interesa investigar herramientas para la construcción de Test ahora, te dejo una lista de algunas de las más populares:

  • Java - JUnit
  • JavaScript - Jest
  • Python - Pytest
  • C# .Net - NUnit

Etapas: Red, Green y Refactor

TDD consiste en tres etapas, que son el motor de esta técnica y se convierten en un ciclo virtuoso e infinito. Estas tres etapas consisten en escribir un test que falle por la razón correcta, hacerlo pasar de la manera más simple y aplicar técnicas de refactor en el código resultante.

image.png

En primer lugar tenemos que identificar, en nuestra planificación, el paso hacia adelante testeable, más pequeño posible. En nuestro caso, queremos hacer una calculadora desde cero y nuestra primer funcionalidad será la de poder sumar. La técnica clásica se trata de tres sencillos pasos que desarrollamos a continuación:

Red

El primer paso que vamos a realizar es escribir un test que falle (RED). Por supuesto, en esta etapa es probable que nuestro código ni siquiera compile. Justamente ahí radica el aporte más importante de TDD al diseño del software, al tener que usar un código que todavía no existe para construir el test, nos obliga a pensar primero cómo queremos usarlo, ahí es cuando estamos diseñando. Para casos más complejos es importante, en este paso, tener una reflexión previa sobre el diseño que vamos a encarar, no en detalle, pero sí un panorama general. Lo que sí podría ayudarnos mucho es hacer una mínima planificación del problema que queremos resolver: qué pasos incluye, qué lugares del código hay que modificar, detalles a tener en cuenta, etc. Al dividir el problema en partes es muy común perderse en el camino y esta planificación nos ayuda a tener un Norte firme y claro.

Nuestro objetivo en este paso, luego de haber diseñado cómo queremos usar este código, es lograr que el test compile. Esto significa crear todas las propiedades y métodos que el test nos haya pedido pero todo vacío o sin valor alguno. Apenas nuestro código compile ya podremos ejecutarlo. Ahí es cuando queremos ver al test fallar por la razón correcta. Llamamos razón correcta a una respuesta válida (de forma) pero equivocada (de contenido). Por ejemplo, sumando 2 y 1, una falla correcta sería un 0 como resultado pero no una excepción. Una razón incorrecta por ejemplo en ese caso sería error de tipos. Razones incorrectas podrían ser también fallas en el escenario que creamos para el test, es decir el estado al que llevamos al programa antes de ejecutar el cuerpo del test. Veamos algún ejemplo de cómo tendríamos el código en esta etapa.

image.png image.png

Veamos ahora un fallo del test por la razón correcta con este código:

image.png

Green

Una vez creado el código con nuestro diseño aplicado, pasamos al segundo paso que se trata de hacer pasar ese test de la forma más sencilla posible (GREEN), sin preocuparnos por el diseño, ni la performance ni nada de eso. En este instante, habiendo visto al test fallar por la razón correcta y ahora verlo en verde, tenemos confianza de que nuestro test nos está protegiendo bien de esa funcionalidad. De hecho es por eso fundamentalmente que lo queremos ver fallar.

En esta etapa, nuestro código tiene el siguiente aspecto:

image.png

Refactor

Finalmente, luego de estos dos pasos, llegamos al momento de limpiar nuestro código (REFACTOR). Comenzando por el código productivo y en un segundo momento por nuestros test, este es el momento de pensar bien nombres de variables, clases y funciones, crear objetos nuevos, eliminar duplicaciones de código, etc. Lo interesante de este paso, es que a cada pequeño cambio, podemos ejecutar los test e ir avanzando con el refactor, con la tranquilidad de que todo sigue funcionando igual. En caso de que algo falle, sabemos exactamente dónde se encuentra el error y tardaremos, nada en volver para atrás y corregirlo.

En nuestro ejemplo no tenemos nada útil para mejorar porque es un caso de uso muy simple. De todas formas me gustaría destacar que es en esta etapa donde, si estamos aplicando bien la técnica más nos vamos a detener. Esto es porque es acá donde podremos aplicar técnicas de diseño, patrones, técnicas de refactor y cosas más avanzadas que espero podamos conversar en este espacio más adelante. Pero a medida que mejoremos nuestra musculatura aplicando TDD, crear el Test y hacerlo pasar será algo casi automático o relativamente sencillo en la mayoría de los casos.

Una recomendación para esta parte: es muy importante, durante el proceso de refactor, no permanecer en rojo mucho tiempo. ¿Qué significa mucho?, dependerá tu experiencia. Esto es porque perderíamos una de las grandes ventajas de usar TDD que es, ante un error, no estar muy lejos del cambio en el código que lo hizo fallar para poder corregirlo rápido. Por esto es recomendable avanzar en el refactor mediante lo que llamamos baby steps, cosa en la que podemos profundizar más adelante, pero básicamente se trata de dar consecutivamente el paso más pequeño posible siempre.

Conclusión

TDD trae muchas ventajas a la experiencia de desarrollo, pero también a la calidad del producto final. De manera muy sencilla y efectiva, aumenta la satisfacción del equipo de desarrollo y también del cliente. Esta es una pequeña introducción a este mundo, pero como siguientes pasos, recomiendo seguir profundizando en técnicas de refactor, escuelas de TDD y otras técnicas de Extreme Programming para complementar. Todos estos temas los iremos tratando en las sucesivas entradas de este blog.