El valor de TDD (3)

Published on Author lopez

Anterior Post

En el primer post de esta serie, ya mencioné el tema simplicidad, y di algún ejemplo. Es un tema fundamental para entender TDD (Test-Driven Development), pero parece ser que es uno de los menos entendidos. Así que me permito insistir hoy en el tema.

Una de las cosas hacia las que empuje TDD es a la simplicidad, pero siempre y cuando pensemos alineados con el flujo de trabajo de TDD. Veamos la respuesta a la pregunta:

¿Por qué TDD empuja a la simplicidad?

Por dos razones: como se escriben los tests primero que el código (y muchas veces, se escribe un solo test, y hasta que no se termina de trabajar en ese test, no se pasa a otro), nos conviene comenzar con tests sencillos de expresar su intención, no comenzar por los tests más complicados. Como ejemplo, pueden ver los commits de mi proyecto SparkSharp (ver posts). Ahí tengo que implementar un concepto llamado Dataset, sobre el que aplicar map y reduce. ¿Acaso escribí los tests más complicados primero? No, siguiendo la premisa de ir por pasos de bebé (“baby steps”), fui planteando los casos más sencillos primero. Por ejemplo, escribí primero tests para Datasets que se alimentan de items enumerables en memoria, antes de pasar a Datasets que se alimentan de archivos locales. Y luego, en algún momento futuro, escribiré y haré pasar a verde tests sobre Datasets que se alimenten de datos remotos. Pero como en la vida, el más largo camino comienza con un solo paso. Y otro paso y otro paso y así.

¿Acaso en ese proyecto implementé directamente la API del proyecto original, Apache Spark? No, la idea es ir planteando casos de uso pequeños de la API, dado un estado, llamar a un método, y escribir lo que se espera obtener como respuesta o como cambio de estado. Por ejemplo, en el proyecto original los Datasets se crean con métodos factoría de un Spark Context. Yo no he tenido necesidad de eso en los tests simples que he escrito. Cuando llegue el caso de necesitar eso, por alguna circunstancia concreta, lo haré, haciendo refactor y rediseño. Pero recuernde: no hay que cruzar el puente ANTES de llegar al puente.

Y la segunda razón: TDD nos lleva a resolver el pasaje del test a verde DE LA FORMA MAS SENCILLA. Eso lo tenemos que poner nosotros, la voluntad de implementar código de producción, APENAS PARA que pase el test. Si sentimos que al código de producción le falta algo (por ejemplo “debería controlar que este parámetro no sea nulo”), entonces ESCRIBIMOS el test correspondiente, y luego agregamos la funcionalidad que falta. Si no hay ningún test que pida a gritos la implementación de algo, ese algo NO SE IMPLEMENTA. Primero el test, luego la implementación. De alguna forma, esto lleva a que casi cada línea de código de producción PUEDA SER TRAZADO su origen a algún test. Línea de producción que no nace de un test, es sospechosa.

Corolario: no agrego librerías y dependencias que no necesito para resolver un test. No digo “no agregar Angular”, o “no agregar Hibernate” nunca. Digo: lo agrego cuando realmente los casos de uso planteados (los casos de la API, lo que espero que haga), realmente justifican el agregado de la dependencia. Y van a ver, que gracias a TDD, el agregado de la librería en general no cuesta mucho. Una de las consecuencias del uso de TDD y simplicidad y lo ágil en general, es que el diferir las decisiones no tiene un gran costo, y hasta mejora nuestro diseño porque permite que lo que se agrega se agregue por necesidad real, y no por “cargo cult programming” o porque sea “la mejor práctica”. Recuerden, no hay “mejores prácticas”, sino “mejores prácticas en contexto”. Además, el diferir la decisión tal vez ayudar a decidir, luego de ver mejor los casos de uso, que en vez de Angular necesitamos React, o que en vez de un ORM necesitamos un MicroORM, y así. Permite que nuestro diseño no vaya anclado a una decisión temprana de tecnología.

Veamos ahora un “pecado” que he observado cuando se intenta comenzar con TDD: pensar demasiado por adelantado. Recuerden, los primeros tests tienen que ser sencillos. Por ejemplo, en mi implementación de una especie de OLAP en memoria, MemOlap, ¿acaso pensé todas las estructuras internas de antemano? Para nada. A medida que implementé casos de uso, fui implementado mejor lo que quería hacer, y refactorizando. El software crece así como un organismo, guiado por nuestras ideas y por el ambiente que lo rodea, los tests que vamos incorporando de a poco. Justamente, MemStore fue la semilla para un proyecto real no público, que avanzó mucho más, y hoy está en producción. Y cuando en ese avance se plantearon nuevos casos de uso, o se vió que había que refactorizar algo, se lo hizo, sin mayor esfuerzo. Doy un caso: cuando se llegó a probar el rendimiento con cientos de millones de registros, se vió que algunas operaciones eran lentas. Ahí se hizo refactor, se implementó el recorrido de los items de una forma más eficientes, sin apelar a crear y destruir objetos a cada momento, y gracias a los tests en verde, todo siguió andando. Hasta hubo refactor quirúrgico: llamo así a los que cambian mucho de la implementación. Y al comienzo de tal tipo de refactor pueden quedar muchos tests en rojo. Pero volviendo todo a verde, al final queda un sistema tan estable como al principio.

En gran parte, el bajo esfuerzo de cambio resultó de la simplicidad adoptada hasta ese momento, y la ayuda de los tests ya escritos, que permitieron rápidamente, ante un refactor (cambio interno de implementación) o un rediseño (cambio de la API porque hay nuevos casos de uso a contemplar), darse cuenta en el momento qué se había roto y qué no.

Entonces, no comenzar a pensar ya en el primer test en repositorios, ORM, y contextos de ejecución. Hay que relajarse y volver a plantear el desarrollo del software como juego. Imaginemos que cuanto más tests en verde tengamos, más avanzamos en el juego.

Hacer de cada test, un test sencillo.

Hacer de cada implementación, la más simple para que pase el test.

No agregar algo a la implementación, sin respaldarlo con test.

Y como siempre, la neurona atenta, vermú con papas fritas y good show! 😉

Nos leemos!

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