El valor de TDD (Test-Driven Development) (1)

En estos últimos años he estado practicando deliberadamente TDD (Test-Driven Development) y trabajo en proyectos personales usando el flujo de trabajo de esta disciplina casi todos los días (ver mis posts de TDD y los commits en GitHub) Y también he trabajado en proyectos profesionales, con y sin TDD. Y luego de tanto código y proyectos, pienso que ya tengo un panorama de cuáles son los valores que agrega TDD a un proyecto que tenga desarrollo de software y cuáles son los problemas que podemos tener si no usamos TDD..


Primero, hay que recordar que un proyecto profesional puede ser más que programar código: he escrito que un proyecto BUENO es el que resuelve un problema (ver Un Buen Proyecto). No es solamente programar código, y menos programar código como nos parece o nos gusta. Lo importante es solucionar el problema que tiene quien nos contrata como equipo o como desarrollador individual.


Hecha esa aclaración ¿que es lo que vi de más valor siguiendo TDD, tanto en proyectos personales como profesionales? El primer punto de valor que veo como evidente, espero poder transmitirles el concepto, es:


TDD NOS EMPUJA A LA SIMPLICIDAD EN EL SOFTWARE QUE CONSTRUIMOS


Como cada vez que escribimos un test tenemos que pasarlo de rojo a verde con el código más simple, entonces esa simplicidad se va transmitiendo a lo que estamos construyendo.


Antes de seguir con el tema simplicidad, recordemos que no es sólo hacer TDD. Por ejemplo, tengo en gran importancia implementar casos de uso (ver TDD y Casos de Uso). Sin una serie de casos de uso a implementar, corremos el peligro de salirnos del carril y escribir algo que nos parece lindo, interesante, pero no soluciona el problema que tenemos que atacar en el proyecto.


Entonces, con TDD y guiado por los principales casos de uso, vamos diseñando un software. TDD es un proceso de diseño de software, no de escribir tests o de testing. TDD nos lleva a construir un software lo más simple posible, y los casos de uso nos guían hacia donde realmente esta el problema a resolver. Me gusta decir que aplicamos “baby steps”, hacia un diseño incremental de la solución (ver TDD y Baby Steps). No digo no hacer diseño de antemano: hago algo de diseño,  pero en la servilleta del desayuno, y apenas un poco. No pongo diseño imaginado en el código HASTA QUE NO LLEGUE EL TEST que lo amerite.


No olvidarse del otro importante paso: el refactor. Es ahí donde vamos a poner lo que conocemos para mejorar el código. Y es notable: TDD, como flujo de trabajo, nos va enseñando sobre diseño de código, de forma incremental. Al llegar un nuevo test, podemos ver que lo que habíamos escrito hasta entonces no sea lo óptimo, y refactorizamos la implementación interna. En este paso puede aparece la aplicación de patrones. Y eso es bueno: aparecen en un contexto, el de refactorización, no aparecen “del aire” o porque se nos ocurre a nosotros que hay que aplicar tal patrón. Recordemos que unos de los atributos de un patrón de software es el contexto donde se aplica. En la etapa de refactor podemos descubrir o poner de manifiesto ese contexto y ahí entonces reción aplicar el patrón. Si hacemos diseño de antemano, podemos caer (y lo he visto suceder) en la “patronitis”: agregar todos los patrones habidos y por haber, porque pensamos que son la mejor práctica. Hacer eso es una variante de “optimización prematura es la madre de todos los males”. Lo que nos va a enseñar TDD es que no hace falta pensar todo de antemano, rompiendo YAGNI. Lo que vamos creando con TDD en general va estar en “buena forma” para adaptarse a los nuevos casos de uso y nuevos tests que aparezcan. 


Y aca esta el otro valor que agrega, paralelo a la simplicidad: no solamente el código va creciendo con la simplicidad necesaria, sino que tambien esta en su mayor parte ajustado a lo que nuestros tests piden, no a lo que nosotros queremos o imaginamos que necesita tener nuestro código. El diseño del software en construcción, de esta forma, es como la confección de un traje a medida, que va calzando perfecto en cada iteración a lo que necesitamos implementar. 


Para que no todo sea cháchara en este post, pongo un ejemplo concreto, que ya he expuesto en otros posts (ver, por ejemplo, Desarrollando una Aplicación usando TDD). Supongamos que necesitamos devolver una lista de nuestros productos por categoría, digamos, en una API. ¿Qué hacemos en el controlador? He visto muchas veces que ya al primer test se le coloca un repositorio “mockeado” porque, claro, necesitamos el patrón repositorio ¿no?. Así nace la “paternitis”. NO: si somos consecuentes con el flujo TDD, lo más simple es ir entregando al controlador…. un LISTA DE PRODUCTOS. Solamente cuando tengamos más casos de uso, y veamos que la lista de productos la necesitamos en otros lados, vamos separando su existencia en otros componentes. He visto también que llegado al primer o segundo test, o aún antes, ya se está diseñando la base de datos para tener una tabla de productos y otra de categorías. Yo pregunto ¿quién, en qué el test, pidió eso? ¿qué test puso persistencia en el tapete? Muchas veces, NINGUNO. De nuevo, hay que ir implementando lo que nos pide el test, nada más, no hay que agregar nuestra propia “bajada de línea”, “ruptura de YAGNI”, como cuando pensamos “vamos a necesitar repositorio, vamos a necesitar persistencia” y entonces, necesitamos YA ponerlo y YA ponerlo como mock y así. Ese es el camino de romper la simplicidad de la que hablo.


De esta forma, el software que tengamos en construcción, crece como un organismo: de a poco, de forma controlada.


Seguiré con el tema del valor que agrega TDD, en los próximos posts: aumenta la calidad de código, permite la evolución y la adaptación al cambio, permite reducir la cantidad de bugs, permite arreglar los bugs detectados de forma más rápida, deja al proyecto en buena forma para que lo tomo otro equipo, etc.


Nos leemos!


Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez


 

This entry was posted in 10549, 11699, 1392. Bookmark the permalink.