Introducción a desarrollo guiado por especificación TDD

Introducción a desarrollo guiado por especificación TDD

En realidad TDD significa Desarrollo guiado por pruebas, del inglés Test Driven Development. Lamentablemente es un nombre que no le hace justicia, lo que realmente guía al desarrollo siguiendo esta práctica, son las especificaciones o comportamientos.

Ya sé, ya sé, para la gente que sabe que es BDD seguro se pregunta, ¿Guiado por comportamiento? ¿Eso no es BDD? Pero el contenido de esta publicación clarificará la estrecha relación entre TDD y BDD. Mostraremos una perspectiva en la que en esencia, son lo mismo.

Origen de TDD en la vida

Una de las características principales y de hecho el primer paso cuando se aplica TDD es el entender bien el problema a resolver y por eso se escriben las pruebas primero, esta idea ha existido desde que el ser humano hace cualquier tipo de trabajo, ejemplo:

Un cazador enseña a su hijo como cazar una cebra y le indica que el mejor lugar para clavarla es justo debajo de la oreja, donde puede atravesar su cráneo, pero ese es un tiro muy difícil. Le dice que el tiro más facil es «Apuntar a las rayas que la cebra tiene en el pecho». Mientras el padre dibuja las rayas en el tronco de un árbol (simplicidad).

– Sigue practicando el tiro a una distancia de 20 pasos hasta que claves la lanza correctamente”, luego -“No espera, sostén la lanza así”, más tarde – “Si, así está mejor, continua así” … – “Ahora, pídele a tu tío que te enseñe cómo hacer que la cebra se acerque a una distancia de 10 pasos» (iterativo e incremental, lotes pequeños, ciclo de retroalimentacion)

Bob Allen de codecraftsmansaturdays

El objetivo del padre y del hijo es que este último pueda cazar una cebra (enfoque). 

Lo ideal es clavar la cebra justo debajo de su oreja. Esto es una tarea difícil para el hijo, entonces utilizando un proceso iterativo e incremental, se empieza con el paso más fácil hasta ahora conocido (lotes pequeños), esto es apuntar al pecho de la cebra a una distancia de 20 pasos (simplicidad).

Después surge otro inconveniente, sostener la lanza correctamente, por eso se trabaja en ello. Posteriormente surge otra cosa, hacer que la cebra se acerque hasta 10 pasos de distancia (lotes pequeños, iteraciones y vamos incrementando hasta la meta). Aunque no se menciona la razón, podemos suponer que el padre se quiere asegurar que el hijo pueda clavar la cebra en el primer tiro y por eso el acercamiento de la distancia.

TDD está basado en principios ágiles

Todo este proceso natural e inconsciente sobre como clavar a una cebra, descrito en la sección anterior, gira alrededor de principios ágiles. Y como todo práctica ágil, TDD sigue estos mismos principios:

  1. Simplicidad, el arte de maximizar el trabajo no realizado, es esencial.
    1. Algunos conocerán los principios KISS y YAGNI en programación.
    2. Así también algunos habrán escuchado NO DESPERDICIAR de Lean manufacturing
  2. Lotes pequeños en cada iteración
  3. Ciclo de retroalimentación
  4. Proceso iterativo e incremental

Seguro hay más, pero estos son los más importantes desde mi punto de vista y tal vez los que más podemos notar en el día a día.

Origen de TDD en programación

Para darte una idea del origen real de TDD, voy a citar varias fuentes, traducidas en español.

We had no manuals for ENIAC. We learned how to program by studying the logical block diagrams. What a blessing. From the beginning, I knew how computers worked. We gained the respect of the engineers from the beginning because we really knew what we were doing and we could debug better than they could because we had our test programs as well as our knowledge of the computer.

Betty Jean Jennings Bartik, ENIAC programmer 1946

El primer ataque al problema puede realizarse antes de comenzar la codificación. Para determinar completamente la precisión de las respuestas, es necesario tener un caso de verificación calculado a mano con el cual comparar las respuestas que luego serán calculadas por la máquina. Esto significa que las máquinas de programas almacenados realmente nunca se utilizan para un problema de una sola vez. Siempre debe haber un elemento de iteración para que valga la pena. Los cálculos manuales se pueden realizar en cualquier momento durante la programación. Sin embargo, con frecuencia las computadoras son operadas por expertos en computación para preparar los problemas como un servicio para ingenieros o científicos. En estos casos, es muy deseable que el «cliente» prepare el caso de verificación, en gran parte porque tal procedimiento puede señalar errores lógicos y malentendidos entre el programador y el cliente. Si el cliente va a preparar la solución de prueba, lo mejor para él es comenzar mucho antes del problema real, ya que para cualquier problema importante se necesitarán varios días o semanas para calcular la prueba

Digital Computer Programming, D.D. McCracken, 1957

Otro dato, de la época de John Von Neumann, en una entrevista realizada a Gerald. M. Weingberg por Michael Bolton

Jerry: We didn’t call those things by those names back then, but if you look at my first book (Computer Programming Fundamentals, Leeds & Weinberg, first edition 1961 —MB) and many others since, you’ll see that was always the way we thought was the only logical way to do things. I learned it from Bernie Dimsdale, who learned it from von Neumann.

then I ran into Bernie (in 1957), who showed me how the really smart people did things. My ego was a bit shocked at first, but then I figured out that if von Neumann did things this way, I should.

John von Neumann was a lot smarter than I’ll ever be, or than most people will ever be, but all that means is that we should learn from him…

Geral M. Weinberg

The original description of TDD was in an ancient book about programming. It said you take the input tape, manually type in the output tape you expect, then program until the actual output tape matches the expected output.

After I’d written the first xUnit framework in Smalltalk I remembered reading this and tried it out. That was the origin of TDD for me. When describing TDD to older programmers, I often hear, “Of course. How else could you program?” Therefore I refer to my role as “rediscovering” TDD.

Kent Beck

Si analizamos un poco estos últimos datos, en esa época se necesitaban ejecutar pruebas y resolver el problema en papel antes de correr el programa en la computadora porque era muy tardado volver a ejecutar el programa después de una falla ya sea por el tiempo de “ejecución” o porque las máquinas de esa época simplemente no eran tan sofisticadas como las de hoy en día.

Todo esto nos indica la importancia de las pruebas y así no retrasar el tiempo necesario para que la solución funcione correctamente y con menos esfuerzo, en la actualidad no ha cambiado, tenemos a lo mejor otros problemas, además el software que se desarrolla hoy en día tiene mayor escala y las funcionalidades son más sofisticadas.

Otro punto que nos enseñan estos datos, es que la comunicación entre los involucrados desde un principio es importante para evitar errores lógicos y malentendidos entre el cliente y el programador. Aquí los clientes también pueden ser  usuarios, personas de UX y product owners.

Por último, nos abre los ojos a que las pruebas siempre han sido parte del proceso de diseño y codificación. Y que se hacen lo más antes posible (test-first programming)

OK, ¿Qué es TDD?

TDD es una práctica ágil, por consiguiente basada en principios ágiles que nos permite tener una guía en el desarrollo de software para nunca perder el enfoque.

Es una disciplina que te permite eliminar el miedo a modificar el código de un software, lo que resulta en la limpieza constante del código, por lo que se asegura que el software escale de manera fluida, sin retrasos y dolores de cabeza permitiendo tener un software lo suficientemente flexible para poder adaptarse a cualquier cambio.

TDD se basa en el mindset de prevenir defectos en lugar de resolverlos después, es obvio que no se puede prevenir todos los casos, pero si un gran número con un conjunto de pruebas bien hechas.

Históricamente hablando ha probado ser en la gran mayoría de los casos, la mejor manera de crear productos de calidad. Significa Desarrollo guiado por pruebas, pero como mencionamos antes, la palabra especificación en lugar de pruebas, la describe mejor, algo así como SDD, Specificacion Driven Development.

Ciclo de retroalimentación Red-Green-Refactor

En la práctica, consiste en tres pasos segun Kent Beck:

  1. RED. Escribe una peña prueba que no funcione, tal vez ni siquiera compile al principio
  2. GREEN. Haz que la prueba funcione rápidamente, cometiendo los pecados necesarios en el proceso.
  3. REFACTOR. Elimina todo el código duplicado creado

Ahora bien, cuáles son los beneficios principales:

  • Ciclo de retroalimentación
  • Crear calidad, no medirla
  • Herramienta de diseño
  • Elimina el miedo a cambiar o mejorar el código
  • Documentación precisa

Ciclo de retroalimentación

Las organizaciones que diseñan sistemas (en el sentido amplio) están limitadas a producir diseños que son copias de las estructuras de comunicación de dichas organizaciones.

Melvin E. Conway, 1968

Un ciclo de retroalimentación es muy importante para todo lo que hacemos, entre más pronto tengamos retroalimentación, más pronto podemos reaccionar y corregir nuestras acciones.

Cualquier metodología, framework o proceso ágil tiene como uno de sus elementos principales uno o varios ciclos de retroalimentación. Por ejemplo, en scrum, cada iteración llamada sprint es un ciclo de retroalimentación, todos los días el daily es otro ciclo de retroalimentación. Cada release o Q es otro ciclo de retroalimentación.

Estos ciclos de retroalimentación nos permiten tener una excelente comunicación y de forma constante.

TDD como ciclo de retroalimentación

Pero hablando en conjunto, desde el punto de vista técnico y de negocio, trasladado al código, el cual permite cumplir con el comportamiento deseado de una determinada aplicación, ¿Existe algún ciclo de retroalimentación? ¿Qué nos permite corregir errores lo más pronto posible?

Claro que sí, existen dos prácticas muy útiles para esto, una se llama BDD y la otra TDD, estas prácticas responden dos preguntas muy importantes:

  1. ¿Estamos construyendo el software correcto?, ¿El que el usuario final necesita?
  2. ¿Estamos construyendo correctamente el software, ¿Con la suficiente flexibilidad que permita adaptarse a las nuevas necesidades de los usuarios?

Aunque BDD y TDD parecen que son dos cosas diferentes, en realidad son lo mismo, TDD se empezó a utilizar primero, de hecho se creaban pruebas de aceptación, de integración y unitarios usando TDD. Luego, apareció ATDD y BDD (con ayuda de DDD) como extensiones de TDD.

Lo que escribimos en código de prueba son el reflejo de las especificaciones, el objetivo de las pruebas no es verificar exactamente que una clase o método se ejecute correctamente, sino cómo se comporta según el deseo implícito de los usuarios finales, y este deseo implícito se plasma explícitamente en las especificaciones creadas antes de empezar escribir código de programación. 

Lo anterior es muy importante porque nos da el objetivo y el foco correcto a nuestro desarrollo.  Esta idea la podemos decir de otras maneras:

  • Desarrollo guiado por especificación
  • Desarrollo guiado por el dominio
  • Desarrollo guiado por comportamiento
  • Desarrollo guiado por pruebas, la definición más conocida y que lamentablemente genera más confusión.

TDD para crear calidad, no para medirla

La palabra Testing (Prueba) en el mundo de QA (Quality assurance), quiere decir probar después de que el software está desarrollado, probar y probar hasta comprobar que el software no tiene defectos. Debido a esto, las pruebas de QA son una forma de medir la calidad de un producto, pero no es una forma de construir un producto con calidad.

El ciclo de retroalimentación que nos da el escribir y terminar una funcionalidad y luego probar es ineficiente porque la retroalimentación se obtiene muy tarde. Es posible trabajar en algo equivocado por minutos, horas y hasta días, a veces ese trabajo tiene que ser desechado o mínimo con muchos cambios.

Cuando la retroalimentación se retrasa mucho tiempo, por ejemplo, después de un ciclo de pruebas por parte de QA, el desarrollador encargado no tiene el contexto o no lo recuerda bien. Y esto genera más gastos, porque una vez reportado por QA se debe de encontrar la fuente del defecto y replicarlo, luego entender el problema, entender el código relacionado y además el tiempo que se tome corregirlo. Y otra vez QA prueba y reporta si el bug ha sido arreglado o no.

De nuevo el principio de Lean manufacturing sobre trabajar sobre cosas simples y pequeñas, no esperar a encontrar errores o mitigarlos hasta el final de la cadena de producción, sino en cada pequeña etapa del proceso tener uno o más ciclos completos de retroalimentación.

Ayuda a devops

Esto último va de la mano con la integración continua y despliegue continuo, si las pruebas fallan, no se puede establecer una integración y mucho menos un despliegue a producción.

Dado que un ciclo de retroalimentación temprana sucede en varios niveles del software, las incidencias aparecen antes de una integración y también mucho antes de un despliegue.

Claro que nunca se podrá mitigar todas los bugs o incidencias, pero por supuesto que se puede disminuir en una gran proporción, esto de igual manera resulta en reducción de costos, tiempo y esfuerzo.

El ambiente en los equipos se vuelve productivo, mejora la comunicación y seguridad en que de verdad se está entregando un producto de calidad, esto produce estimaciones más precisas, seguras, y honestas.

Herramienta de diseño

Al escribir las pruebas primero (test-first programming), nos enfocamos a que el código de producción sea accesible desde el código de pruebas y sea fácil de probar. Esto quiere decir que desde un inicio nos preocupamos por el diseño y la arquitectura de nuestro código de producción, porque para que nuestro código de producción sea accesible y fácil de probar debe estar desacoplado.

Al escribir las pruebas, nuestra perspectiva de programador cambia, ahora pensamos en como otros programadores van a usar la API de nuestra funcionalidad mucho antes de crearla, también desde la perspectiva de negocio porque cuando escribimos nuestras pruebas, a menudo nuevas dudas surgen que debemos indagar con personas que no son necesariamente programadores.

Cada una de nuestras pruebas deben ejecutarse independientemente de otra, en  nuestro esfuerzo de hacer que las pruebas se ejecuten independientemente, mejoramos el diseño del software, por ejemplo, una prueba unitaria corre dentro de un ambiente que no es real, se ejecuta sobre lo que llamamos test fixtures y sus dependencias (muchas de las cuales serán código de producción) deben ser lo bastante flexibles de tal manera que permitan ser sustituidas, invocadas y verificadas fácilmente. Esto permite crear un software con alta cohesión y bajo acoplamiento, es decir, con un buen diseño y una buena arquitectura.

Our designs must consist of many highly cohesive, loosely coupled components, just to make testing easy.

Kent Beck

El código de pruebas debe desarrollarse con la más alta calidad posible, deben ser fácil de entender y fácil de ejecutar de manera independiente, y al enfocarnos en este aspecto, el código de producción creado también resulta de la más alta calidad posible. Sin perder de vista que esto sucede paso a pasito, en un proceso enfocado a la simplicidad, iterativo e incremental.

El resultado de un buen diseño, también resulta en que las modificaciones de código se hacen mucho más rápido, y esto quiere decir que no necesitas quedarte horas extras programando, tendrás más tiempo personal, que puedes compartir con tu familia, hijos, con tu pareja (o amante), etcétera.

Elimina el miedo a modificar el código

¿Por qué la mayoría de los programadores tienen miedo de hacer cambios a su código? ¡Tienen miedo de romper el código! ¿Por qué tienen miedo de romper el código? Porque no tienen pruebas.

Robert C. Martin, The Clean Coder: A Code of Conduct for Professional Programmers

Las pruebas son más importantes que el código de producción porque permiten que el sistema se vuelva flexible, de otra manera el miedo a la modificación del código crece, porque se teme romper la funcionalidad, y entonces el software ya no es adaptable, poco a poco, entre más evoluciona, menos flexible se vuelve por miedo a realizar modificaciones que rompan el producto.

¿No te ha pasado en algún proyecto, donde necesitas hacer unas modificaciones al código, y existen partes que mejor ni las tocas por miedo de romper alguna funcionalidad?, porque realmente no estás seguro de lo que esa parte del código hace y no tienes un conjunto de pruebas que respalde tus cambios.

¿Qué es código legacy?

Codigo que developers tienen miedo de cambiar

Eli Lopia CEO de typemock

Código sin pruebas

Michael C. Feather

TDD permite mitigar el miedo de modificar tu código o el de otra persona. ¿Cómo puedes tener seguridad de que las funcionalidades que desarrollas tienen la calidad suficiente para decir que está terminado? Sin un conjunto de pruebas no tienes esa seguridad, y como ya hemos comentado antes, esperar retroalimentación hasta el ciclo de pruebas de un equipo de QA, es demasiado tarde.

Cuando se tiene un buen conjunto de pruebas, no tienes miedo de hacer los cambios necesarios, porque si rompes algo, las pruebas te lo advierten casi en tiempo real mientras estás realizando las modificaciones.

Segun Ken Beck el miedo tiene los siguientes efectos:

  • Te hace inseguro sobre el código que escribes
  • Te hace querer comunicar menos
  • Te hace tímido a la retroalimentación
  • Te hace gruñón

Ningún efecto de arriba es de ayuda a la hora de programar y menos cuando nos enfrentamos a un problema complicado. Así que Kent Beck recomienda los siguientes puntos usando TDD:

  • En lugar de dudar de tu código, empieza a aprender cosas concretas lo más rápido posible. Paso a pasito, lotes pequeños y de manera iterativa e incremental con el ciclo red-green-refactor.
  • En lugar de quedarte callado, comunícate más claramente. Al usar TDD aumentas la calidad y por consiguiente estás dispuesto a comunicar dudas o problemas que enfrentas a la ahora de programar
  • En lugar de evitar retroalimentación, busca ayuda, y retroalimentación concreta.
  • (Sobre lo gruñón, estas por tu cuenta). Pero con más seguridad en tu código, menos problemas y más tiempo personal, seguro que tu nivel de gruñón bajará.

Documentación precisa

Para tener una documentación detallada es necesario mucho tiempo y esfuerzo porque la documentación siempre cambia conforme se le agregan nuevas funcionalidades o se modifican las ya existentes. Entonces ese tiempo y esfuerzo mejor se invierte en obtener software funcionando que puede ser presentado al cliente y obtener retroalimentación. Es muy difícil mantener sincronizada la documentación con el verdadero funcionamiento (con el código), con el tiempo la documentación miente sobre la funcionalidad, la única fuente de verdad es el código, el código no miente.

La mejor documentación e instrucciones de como funciona una pieza del sistema se encuentran en las personas y el código que ellas crean, es de valor inigualable poner énfasis en la excelencia técnica y al buen diseño para mejorar la agilidad, es decir, código limpio, bien diseñado y utilizando TDD para que cualquier integrante lo pueda entender y aumentar nuevas funcionalidades y modificar existentes con gran facilidad. Incluso para que las mismas personas que crearon puedan recordar los detalles más minúsculos que te puedas imaginar. De hecho a veces no necesitas llegar al detalle, simplemente utilizas la pieza del sistema con seguridad del resultado debido al buen diseño y a la calidad de la misma, lo que por supuesto te ahorra muchísimo tiempo. Claro que esto no se puede lograr sin las personas, sin su disciplina y auto organización.

Para ponerlo más claro, cuando tienes la disciplina de hacer TDD, cada una de las pruebas que creas, describe perfectamente como cada función o método, clase y/o componente se utiliza, de una manera tan precisa que si algo va mal, al menos una de las pruebas fallara casi al instante, cuando se está modificando el código.

Cuando realizas pruebas de métodos y funciones, tienes que describir que parámetros reciben y que resultados regresan ¿Qué más documentación necesitas? Tampoco digo que nunca se haga documentación, claro que si, pero es una inversión de tiempo, dinero y esfuerzo, que puedes disminuir considerablemente y que puedes posponer hasta las etapas más estables del producto, cuando se sabe que su funcionalidad cambiará poco o en intervalos más largos de tiempo.

Al final como programador, siempre nos aseguramos o confiamos al 100% sobre como funciona cualquier API de software, hasta que le echamos un ojo al código. Y sería estupendo que la funcionalidad del código esté descrita detalladamente a través de un conjunto de pruebas.

Referencias

https://arialdomartini.wordpress.com/2012/07/20/you-wont-believe-how-old-tdd-is/

http://homepages.cs.ncl.ac.uk/brian.randell/NATO/nato1968.PDF

https://www.developsense.com/blog/2011/01/jerry-weinberg-interview-from-2008/

https://associationforsoftwaretesting.org/2014/11/02/how-to-sell-tdd-to-x/

https://www.computerworld.com/article/2470893/jean-bartik–last-of-the-original-eniac-programmers–86.html

https://en.wikipedia.org/wiki/ENIAC

https://es.wikipedia.org/wiki/Jean_Jennings_Bartik

https://www.quora.com/Why-does-Kent-Beck-refer-to-the-rediscovery-of-test-driven-development-Whats-the-history-of-test-driven-development-before-Kent-Becks-rediscovery

https://agileforall.com/history-of-tdd-as-told-in-quotes/

Test-Driven development: by example

Deja un comentario

A %d blogueros les gusta esto: