TDD es una técnica que nos dice que, antes de construir código que expresa la solución para un requerimiento dado (que es el circuito clásico), expresemos esos requerimientos (de tenor técnico) como pruebas que debería pasar la funcionalidad que vamos construir. En otras palabras: primero decimos cómo se validará nuestro requerimiento a nivel de código antes de escribir ese código que cumplirá ese requerimiento. Muy rarito y a contramano de lo que estamos acostumbrados.
Repito, suena un poco raro, pero tiene un sentido que pasaremos a explicar un poco más adelante.
Como quizás sabemos, si han leído algo sobre el Agilismo en el software, la idea es trabajar lo mas que se pueda con el interesado en el producto o quien sepa del negocio entre manos para poder desentrañar qué es lo que realmente desean con el software: me refiero a saber definir sus requerimientos. En otras palabras, para representar los requerimientos como “pruebas” primero se debe entenderlos. A los requerimientos lo intentamos interpretar nosotros con la ayuda del/los stakeholders si es necesario (para eso el agilismo los hace participar en el equipo de gente que construirá el producto para él). El corolario es que si entendemos mal el requerimiento, inadecuadas también serán las pruebas que escribamos.
La base de TDD son los Unit Test que es el nombre que recibe ese “código” que representa el requerimiento que se validará. Esto es, Unit Test es un “método” que prueba una unidad de trabajo entendiéndose a esta como un método, un conjunto de ellos o una propiedad; la idea es que la unidad de trabajo es un “todo coherente” que ejecuta y da respuesta a nuestro requerimiento.
Vamos a necesitar el requerimiento del usuario, que puede estar expresado como una User Story, comprendido por nosotros (si no lo entendemos bien deberíamos retroalimentarnos con el owner) Pero para poder derivar una prueba seria aconsejable tener la Historia de Usuario desglosada en tareas puntuales que nos sirvan de guía para escribir los Unit Test, este trabajo ya es nuestro porque se supone que entendimos aproximadamente bien la Historia.
En TDD usamos ciertos colores para representar las tres fases por las que pasa el trabajo de escribir un Unit Test (UT) y el código que la satisface: Rojo, Verde y Refactoreo.
Los UT los podemos escribir a “mano” completamente o nos valemos de los frameworks disponibles que nos ayudan en un montón de tareas de configuración de manera que nos concentremos en lo que realmente tiene valor lo cual es expresar un requerimiento como una prueba y ejecutarlo.
Vamos a usar el framework que viene con Visual Studio que es el Visual Studio Unit Testing Framework pero también tenemos uno muy bueno como NUnit que es free y open source y sirve para. NET.
Volvamos a las fases de TDD y veamos que son y para qué sirven:
Se supone que procedemos así:
Tenemos nuestro requerimiento expresado en algo más operacional para el developer, tenemos más o menos en claro cuál la prueba que necesitamos. Escribimos la prueba con la ayuda de nuestro framework, lo que resultará en un método como cualquier otro solo que en su cuerpo pondremos todo lo necesario para validar lo que le pediremos a nuestro software y lo que este devolverá.
- Vamos a comenzar a escribir código y la idea es que inicialmente pase por dos subfases: primero que no compile (porque habrá clases y métodos que no existen todavía) y segundo que deliberadamente no supere la prueba
pero siendo ya código compilable. Aquí vamos a ver salir nuestro primer color ROJO porque el framework de UT automáticamente nos avisará que nuestro código no pasa la prueba que le pusimos (pero sí compila). Vamos ser mas claros: armamos nuestro test y ESCRIBIMOS el código que nosotros suponemos que será necesario llamar desde el UT para que esta pase. En resumen: escribimos código compilable que no pase la prueba es decir el framework de UT nos lo dirá con un color rojo.
- Seguidamente escribimos la mínima cantidad de código que nos hará superar la prueba propuesta por el UT. La MINIMA implica escribir lo mas elemental que podamos, así sea código hardcodeado pero que sabemos que superara la prueba. De nuevo, esto suena loco, pero de esta forma obtendremos nuestro primer color VERDE.
- Y finalmente cuando ya tenemos nuestro color Verde, vamos a refactorear si es necesario para reorganizar nuestro código teniendo en mente los patrones más usuales así como los code-smells conocidos. “Limpiamos” nuestro código y ejecutamos de nuevo el test automático esperando nos dé un color verde nuevamente. Esto será la señal de que no hay nada más que hacer por ahora con ese UT puntual pues ya lo pasamos.
- Luego vamos a nuestra bolsita de “requerimientos refinados” y escogemos el siguiente para aplicarle la misma secuencia de pasos que vimos antes.
Por otro lado vale aportar que existe toda una discusión sobre si esto es posible, si no será necesario hacer antes un pequeño “boceto” de la funcionalidad, si comenzar con la mente vacía y comenzar a armar paso a paso el diseño como dice crudamente TDD. Es por ello que voy a dejar un link muy interesante sobre esta discusión: Los varios significados de TDD. De mi parte me quedo con la idea de que hay que comenzar con un pequeño esquema mental (algo así como un up-front design pero humilde) antes de desarrollar un requerimiento siguiendo TDD . Después uniendo lo previo con el paso a paso de TDD me parece que funcionaria mejor siendo más accesible.
Ejemplo: Cazando el Wumpus
Voy a construir un pequeño ejemplo que use una vez en una charla interna sobre TDD que di en la empresa donde laburo. La idea es que la funcionalidad es para un jueguito muy primitivo y antiguo (nació en los 70’) que se llama “Atrapando al Wumpus”. La esencia del juego es que tenemos dos players: el monstruo Wumpus, quien está escondido en una cueva, y yo . Por otro lado, tengo que ir aleatoriamente por diferentes cuevas y si me cruzo con el wumpus pierdo, y para eso el sistema me debe pedir a qué cueva deseo ir. Si paso por cinco cuevas sin cruzarme con el Wumpus, gano; de lo contrario pierdo. Esta es la versión más simple de juego y es la que intentare desarrollar lo mejor que pueda con TDD.

Requerimientos:
- Dar una posición inicial y aleatoria al Wumpus.
- Dar una posición inicial y aleatoria al jugador.
- La posición inicial del jugador no debe ser igual a la inicial del Wumpus.
- El jugador puede moverse por las diferentes cuevas.
- Cada vez que el jugador se ubica en una nueva cueva, cambia de posición el wumpus.
- El jugador tiene cinco oportunidades para moverse.
- El juego debe informar al jugador en qué cueva está ubicado actualmente.
- Si la posición del jugador coincide con la del wumpus, pierde el juego.
- Si el jugador nunca se encuentra con el Wumpus, gana.
Creamos el proyecto de Test
Tomamos el primer requerimiento y escribimos el test como lo imaginamos, sin crear las clases necesarias todavía solo escribiendo en el código del UT cuales serian las necesarias (intenciones). En mi caso imagino esto:
1:
2: [TestMethod]
3: public void Wumpus_Tiene_Una_Posicion_IniciaAleatoria()
4: {
5: //Arrange
6: var juego = new Juego();
7:
8: //Act
9: juego.DarPosicionInicial(EnumPlayers.Wumpus);
10: var cuevaActual = juego.ObtenerPosicionActual(EnumPlayers.Wumpus);
11:
12: //Assert
13: Assert.IsTrue(cuevaActual.cueva > 0);
14: }
Ahora lo que debemos hacer es que esto compile creando las clases, métodos y enumeraciones en el proyecto del Wumpus. Primero hacemos referencia al assembly de nuestro juego desde el proyecto de UT:
Y luego en el proyecto del Juego creamos todas las clases, métodos y enumeraciones que pusimos en nuestras “intenciones” en la prueba:
Una vez que tenemos código compilable escribimos el mínimo código en la funcionalidad para que NO pase la prueba. Es decir, buscamos el color Rojo. En nuestro caso vemos que podemos dejar como están implementados por defectos los métodos, que lanzan una excepción y con eso ya tenemos nuestro primer Rojo:
1: public class Juego
2: {
3: public void DarPosicionInicial(EnumPlayers enumPlayers)
4: {
5: throw new NotImplementedException();
6: }
7:
8: public Cueva ObtenerPosicionActual(EnumPlayers enumPlayers)
9: {
10: throw new NotImplementedException();
11: }
12: }
Probamos el test y vemos el resultado, pero antes de probar debemos por un lado activar o hacer visible el Test Explorer y luego compilar toda la solución para que nuestro test sea detectado por el Test Explorer:
Ya tenemos el Rojo por lo tanto ahora debemos buscar el Verde escribiendo la mínima cantidad de código para pasar la prueba, veamos:
Y voila! Tenemos nuestro primer Verde:
Ahora lo que sigue es refactorear si es necesario lo que escribimos de código y luego volvemos a correr el test, veamos.
A mi se me ocurre que lo que podría refactorear es la propiedad “cueva” que indica la“coordenada de la Cueva, le voy a poner un nombre mas adecuado tal como “Posición”:
Corremos el test y vemos que seguimos con Verde! Con lo cual sabemos que dejamos por un rato este requerimiento aquí y buscamos otro.
Seguimos con los otros requerimientos en otra entrega…to be continue
Links interesantes sobre discusiones sobre TDD y sus implicancias
No hay comentarios.:
Publicar un comentario