¿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.
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.
Además de los beneficios mencionados en la sección anterior, el TDD también le consigue:
Lo que todo esto significa para su negocio, es:
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.
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.
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.
Src: Spec-india.com
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.
El Desarrollo Dirigido por Pruebas significa pasar por tres fases. Una y otra vez.
¿Ya está? Sí, eso es todo.
Pero espera, hay más.
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.
¿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:
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.
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á.
Ejecute las pruebas y sí, la prueba falla. ¡Sí!
Ahora cree la implementación más simple que hará que la prueba pase.
¿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.
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.
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».
Y ejecutar todas las pruebas para ver esta falla. (¡Y ninguna otra!)
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.
Ejecute todas las pruebas para asegurarse de que cada una de ellas sigue pasando.
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.
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
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.
Practicar TDD no es un camino de rosas. Al menos no al principio. He aquí las razones.
Se puede fracasar en la práctica de TDD de muchas maneras:
El desarrollo dirigido por el comportamiento y el desarrollo dirigido por las pruebas son similares y diferentes al mismo tiempo.
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.