¿Qué es el desarrollo basado en pruebas (TDD)?

Agile Tdd 1

Desarrollo Dirigido por Pruebas o TDD

¿Recuerda ese día no hace mucho tiempo? Fue al final de una larga y dura semana de terminar la nueva versión. Pero en lugar de celebrarlo, estaban todos frenéticos intentando averiguar cómo solucionar un error. Había gente de soporte respirando en la nuca porque los clientes les perseguían. Esa tarde, usted decidió: «Nunca más». O, al menos, no con una regularidad que aplasta el alma. Desde entonces, ha encontrado la respuesta: El Desarrollo Dirigido por Pruebas (TDD). Y en este artículo aprenderá qué es, qué se consigue con él y cómo se hace.

¿Qué es el desarrollo dirigido por pruebas (TDD)?

El Desarrollo Dirigido por Pruebas es un proceso en el que se escriben las pruebas antes de escribir el código. Y cuando todas las pruebas pasan, se limpia la cocina: se mejora el código.

Sin embargo, el Desarrollo Dirigido por Pruebas no es sobre las pruebas

La premisa detrás del desarrollo dirigido por pruebas, según Kent Beck, es que todo el código debe ser probado y refactorizado continuamente. Eso suena como si se tratara de pruebas, así que ¿qué pasa?

Bueno, las pruebas que escribes en TDD no son el punto, sino el medio.

El punto es que al escribir pruebas primero y esforzarse por mantenerlas fáciles de escribir, estás haciendo tres cosas importantes.

  • Está creando documentación, especificaciones vivas y nunca obsoletas (es decir, documentación).
  • Está (re)diseñando su código para hacerlo y mantenerlo fácilmente comprobable. Y eso lo hace limpio, sin complicaciones y fácil de entender y cambiar.
  • Está creando una red de seguridad para hacer cambios con confianza.

¿Cuáles son los Beneficios del Desarrollo Dirigido por Pruebas?

Además de los beneficios mencionados en la sección anterior, el TDD también le consigue:

  • Notificación temprana de errores.
  • Diagnóstico sencillo de los errores, ya que las pruebas identifican lo que ha fallado.

Lo que todo esto significa para su negocio, es:

  • Mejora su factor de bus, ya que el conocimiento no está solo en las cabezas y facilita la incorporación de nuevas contrataciones.
  • Reduce el coste de las mejoras. Mantener el código limpio es también la forma de minimizar el riesgo de complicaciones accidentales. Y eso significa que podrá mantener un ritmo constante en la entrega de valor.
  • Con la red de seguridad, los desarrolladores están más dispuestos a fusionar sus cambios y a incorporar los de otros desarrolladores. Y, lo harán más a menudo. Y entonces el desarrollo basado en el tronco y la integración, entrega y despliegue continuos pueden despegar realmente.
  • Disminuye el número de errores que se «escapan» a la producción y eso reduce los costes de soporte.

La Sorprendente Razón para Utilizar el TDD

Si todos estos beneficios no son suficientes, hay una razón más para usar TDD, una que le sorprenderá.

Kent Beck lo expresa de esta manera: «Sencillamente, el desarrollo dirigido por pruebas tiene como objetivo eliminar el miedo en el desarrollo de aplicaciones».

El miedo es bueno para mantenerse vivo, pero es un asesino para el trabajo que necesita la más mínima cognición.

¿Y qué hay de «Los desarrolladores no deben escribir las pruebas para probar su propio código?”

Sí, hay buenas razones para no dejar que los desarrolladores escriban las pruebas para probar su propio código.

Sin embargo, este consejo se aplica a las pruebas a nivel de aplicación.

Para las pruebas a nivel de desarrollador, hacer que otra persona escriba las pruebas es como enganchar el caballo detrás del carro.

Los requisitos suelen estar varios niveles de abstracción por encima del código necesario para implementarlos. Así que tiene que pensar bien lo que necesita hacer y cómo lo hará. Escribir las pruebas primero es una forma excelente y eficiente de hacerlo.

Y, recuerde, TDD no es sobre las pruebas.

¿Y dónde encaja el desarrollo dirigido por pruebas en Agile?

Ya en 1996, el equipo del proyecto C3 de Chrysler practicaba la programación basada en las pruebas. «Siempre escribimos pruebas unitarias antes de liberar cualquier código, normalmente incluso antes de escribir el código», dice el estudio de caso sobre el proyecto titulado “Chrysler Goes to “Extremes”.

Escribir primero las pruebas era sólo una de las prácticas utilizadas por el equipo C3. En conjunto, estas prácticas se conocieron como programación eXtreme. Tres miembros de ese equipo, Kent Beck, Martin Fowler y Ron Jeffries, estuvieron entre las personas que escribieron y firmaron por primera vez el Manifiesto Ágil.

El desarrollo basado en pruebas es una práctica ágil fundamental. Apoya directamente el valor ágil de «el software de trabajo por encima de la documentación exhaustiva». Y lo hace protegiendo el software de trabajo con pruebas y creando la documentación como un subproducto natural.

Bien, ¿Cómo se Practica el TDD?

El desarrollo dirigido por pruebas es engañosamente sencillo. Es fácil explicar lo que se hace, pero no es tan fácil hacerlo. Más sobre el porqué de esto, en la siguiente sección. En primer lugar, vamos a explicar lo que se hace en TDD.

The Cycle and Stages of TDD

El Ciclo y las Etapas del TDD

El Desarrollo Dirigido por Pruebas significa pasar por tres fases. Una y otra vez.

  • Fase roja: escribir una prueba.
  • Fase verde: hacer que la prueba pase escribiendo el código que vigila.
  • Fase azul: refactorizar.
¿Ya está? Sí, eso es todo. Pero espera, hay más.

Las Reglas del TDD según el Tío Bob

El tío Bob (Robert C. Martin) expuso las reglas de TDD en el capítulo 5 Desarrollo Dirigido por Pruebas de su libro The Clean Coder.

  • No se permite escribir ningún código de producción a menos que sea para hacer pasar una prueba unitaria que falla.
  • No se permite escribir más de una prueba unitaria que sea suficiente para que falle; y los fallos de compilación son fallos.
  • No se permite escribir más código de producción que el suficiente para pasar la prueba unitaria que falla.

¿Por qué seguir estas reglas? Porque están pensadas para hacerle la vida más fácil.

Pero no estoy contento con la regla #2. Porque tratar los errores de compilación como fallos puede enmascarar el hecho de que una prueba no tiene aserciones. Y eso es malo, porque puede hacerle pensar que una prueba está pasando cuando el código está (en parte) sin escribir o simplemente equivocado.

Dicho esto, la intención de las reglas es mantener las cosas enfocadas en cada fase y evitar que se caiga en agujeros de conejo. Por experiencia, eso ayuda mucho a mantener las cosas claras en su cabeza. Así que:

  • Durante la fase roja (escritura de la prueba), trabaje sólo en el código de prueba.
  • Una prueba que falla es buena. Al menos si es la que estás trabajando. Todas las demás deben ser verdes.
  • Durante la fase verde (hacer que la prueba pase), sólo trabaja en el código de producción que hará que la prueba pase y no refactorices nada.
  • Si la prueba que acaba de escribir falla significa que su implementación necesita trabajo. Si otras pruebas fallan, ha roto la funcionalidad existente y necesita retroceder.
  • Durante la fase azul (refactorización), sólo refactoriza el código de producción y no haga ningún cambio funcional.
  • Cualquier prueba que falle significa que ha roto la funcionalidad existente. O bien no ha completado la refactorización, o necesitas dar marcha atrás
A veces encontrará oportunidades para refactorizar el código de prueba. Por ejemplo, cuando tenga un montón de pruebas [Fact] separadas en xUnit que sólo difieren en los argumentos que pasan al método bajo prueba. Puede reemplazarlos con una sola prueba [Theory]. Mi consejo: no refactorice, sino que añada la teoría y cuando ésta esté de acuerdo con los hechos, puede eliminar los hechos.

Ejemplo de uso del TDD

Hagamos todo lo anterior un poco más concreto con un ejemplo. Supongamos que se le encomienda la tarea de crear un método que convierta los números decimales en romanos.

Paso 1: Fase roja, escriba una prueba.

El decimal 1 debe devolver «I».

Ejecutar la prueba no es posible en este momento, porque la clase Romanizer no existe todavía y eso significa errores de compilación.

Paso 2: Fase verde, hacer que la prueba pase

Añada la clase Romanizer y dele un método FromDecimal que tome un entero y devuelva una cadena.

Me gusta asegurarme de que la prueba sigue fallando cuando el código se compila. Así sé que empiezo con una prueba que falla como resultado de las aserciones en las pruebas.

Así que escribo una implementación que estoy seguro que fallará.

¿Tonterías? No. El código es correcto y hace que la prueba pase..

Idear un algoritmo que pueda funcionar es prematuro a estas alturas. Es posible que acierte, pero es más probable que no lo haga. Y eso puede ponerle en un aprieto (ver más abajo).

Paso 3: Fase azul, refactorizar

No hay mucho que refactorizar todavía, así que a la siguiente prueba.

Paso 4: Fase roja, escribir una prueba

El decimal 2 debería devolver «II».

Ejecutar todas las pruebas para ver esta falla. Esto es muy importante. Le permite estar seguro de que es su implementación la que hará que pase y no la falta de aserciones en su prueba.

Hmm. I don’t like if-else-if constructs much, but let’s run the tests first.

Image13 1

Bien. Pasamos a la fase verde.

Paso 5: Fase verde, hacer que la prueba pase

De nuevo, escribe el código más sencillo que haga que la prueba pase.

Image6 1

Step 7: Red phase, write a test

Decimal 3 should return “III”.

Hmm. No me gustan mucho las construcciones if-else-if, pero hagamos primero las pruebas.

Y sí, hace que todas las pruebas pasen. Paso 6: Fase azul, refactorización Esa construcción if-else-if no es muy elegante, pero dos casos no merecen ser refactorizados todavía, así que a la siguiente prueba.

Paso 7: Fase roja, escribir una prueba

El decimal 3 debería devolver «III».

Image1 1

Y ejecutar todas las pruebas para ver esta falla. (¡Y ninguna otra!)

Image9 2

Paso 8: Fase verde, hacer que la prueba pase

De nuevo, el código más sencillo para hacer pasar la prueba.

Esto empieza a oler muy mal, pero primero haga las pruebas.

Sí, todo verde, así que por fin podemos hacer algo con esa construcción if-else-if, porque 3 casos sí merecen una refactorización ya que ahora se aplica la Regla de 3.

Paso 9: Fase azul, refactorización

El patrón está claro. Se necesita tantos «I» como el número que se ha pasado.

Image5 1

Ejecute todas las pruebas para asegurarse de que cada una de ellas sigue pasando.

Image14 2

Algunas reflexiones adicionales

Por supuesto, sabiendo cómo funcionan los números romanos, el patrón no se mantendrá. Pero romperse la cabeza con un algoritmo de antemano no es el camino a seguir. Lo más probable es que usted termine ajustándolo para cada prueba que añada. Y se pondrá de mal humor cuando cada retoque haga fallar diferentes pruebas. Cuando se escribe el código más simple y se refactoriza, se hace crecer el algoritmo a medida que se avanza. Una manera mucho mejor y más fácil de hacerlo.

Reglas de Refactorización

Cuando se refactoriza, no se hace al azar. Sino que sigues un proceso estructurado para limpiar el código.

Se identifican los olores del código y se aplica una refactorización específica para eliminarlos. Martin Fowler escribió el libro sobre cómo hacerlo: Refactoring: Improving the Design of Existing Code.

El propósito de la refactorización es mejorar la extensibilidad de su código mediante

  • mejorar la legibilidad
  • facilitar la realización de cambios
  • reducir la complejidad
  • mejorar la arquitectura interna (modelo de objetos) y hacerla más expresiva

Lo que es realmente importante es que la refactorización es lo que consigue un código limpio y sin complicaciones que es fácil de entender, cambiar y probar. Y ya ha leído lo que se consigue con eso.

¿Por qué es tan difícil practicar TDD?

Practicar TDD no es un camino de rosas. Al menos no al principio. He aquí las razones.

  • Porque hay que pensar en lo que se quiere conseguir con el código y en cómo protegerlo para que no se rompa (probarlo).
  • Porque tiene una curva de aprendizaje muy pronunciada. Es necesario aprender los principios y patrones de diseño para crear un código limpio y cómo refactorizarlo para mantenerlo así.
  • Porque el código se defiende. El código existente que no está bajo prueba te pilla entre la espada y la pared. Necesita refactorizarlo para ponerlo bajo prueba y necesitas pruebas para refactorizarlo. Recomiendo encarecidamente Working effectively with Legacy Code de Michael C. Feathers para esto.
  • Porque se experimentan los costes de TDD inmediatamente y el coste de no hacerlo mucho más tarde. El atractivo de dejarlo pasar es fuerte. Usted sabe que va a pagar el precio cuando los informes de errores comienzan a inundar. Pero eso es más tarde, ¿no?

¿Cómo se puede fracasar en el TDD? ¿Cuáles son los errores más comunes?

Se puede fracasar en la práctica de TDD de muchas maneras:

  • No seguir el enfoque de «primero la prueba».
  • No refactorizar todo el tiempo.
  • Escribir más de una prueba a la vez.
  • No ejecutar las pruebas con frecuencia, perdiendo la retroalimentación temprana de las mismas.
  • Escribir pruebas que son lentas. Todo el conjunto debería completarse en minutos o incluso en segundos.
  • Usar sus pruebas unitarias para hacer pruebas de integración. No hay nada malo en utilizar su marco de pruebas unitarias para ejecutar pruebas de integración. Pero las pruebas de integración son, por naturaleza, lentas, así que debes ponerlas en su propio conjunto de pruebas.
  • Escribir pruebas sin aserciones.
  • Escribir pruebas para el código trivial, como los accesores y las vistas sin lógica.

Recursos para Aprender y Mejorar

  • The original text on TDD: Test Driven Development, By Example by Kent Beck.
  • Working effectively with Legacy Code by Michael C. Feathers.
  • Refactoring: Improving the Design of Existing Code by Martin Fowler.
  • The Art of Unit Testing: With Examples in c# by Roy Osherove
  • Growing Object-Oriented Software, Guided by Tests by Steve Freeman
  • Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin (Uncle Bob)
  • The Clean Coder: A Code of Conduct for Professional Programmers by Robert C. Martin (Uncle Bob)

¿Cuál es la diferencia entre TDD y BDD??

El desarrollo dirigido por el comportamiento y el desarrollo dirigido por las pruebas son similares y diferentes al mismo tiempo.

No espere, Vaya Tras Su Lecho De Rosas

Como ha visto, el Desarrollo Dirigido por Pruebas es una técnica sencilla de describir, pero mucho más difícil de practicar. Como con cualquier habilidad, necesitará practicar mucho para trasladarla de su cabeza a sus huesos. Para convertirlo en algo natural y llegar al punto en que desarrolle más rápido con TDD que sin él. Pero, como también ha oído, las recompensas son dulces, muy dulces. Desarrollar con confianza. No temer nunca una nueva versión. Entregar valor a un ritmo predecible y sostenible. Un bonito lecho de rosas. Así que, atrévase. Cuanto antes empiece, antes adquirirá la práctica y antes cosechará los beneficios.

¡La vida es buena cuando sus equipos ágiles están sincronizados!

Solicite una demostración personalizada de SwiftEnterprise.