Photo by David Travis on Unsplash

TDD: El bueno, el malo y el testeado

Adrián Ferrera González
Lean Mind
Published in
12 min readJul 17, 2018

--

En esta publicación, me alejaré un poco de la visión tecnológica que he plasmado en mis otros artículos, para dar un punto de vista más personal acerca de mis sensaciones estos primeros días en Lean Mind y el aprendizaje realizado en la formación interna que hemos recibido.

Antes de empezar me gustaría definir distintos conceptos que giran entorno a esta metodología.

TDD

Tras varios años en el sector, siempre he escuchado muchas virtudes de esta metodología, pero en contraposición, muchos compañeros me han transmitido que es algo completamente innecesario. Ante todo, considero que se debe tener una opinión propia al respecto y que la mejor forma es probarlo. Pero antes… ¿Qué es TDD?

El Desarrollo guiado por pruebas o Test-driven development (TDD) es una práctica en el desarrollo del software en la cual se realizan los test unitarios del código antes que la funcionalidad que se desea desempeñar. A medida que se definen casos de uso en los test, podremos ir implantando la lógica del código para que nuestros tests pasen de Red a Green*.

*Nota: No se utiliza la palabra error o correcto, sino que se referencia al color del test.

Una vez que determinemos que el código ya se encuentra en un estado avanzado de la funcionalidad planteada en el caso de uso, debemos refactorizarlo, para obtener una mayor limpieza y claridad de la misma.

En ocasiones la idea de plantear una prueba de algo que aún no funciona suena bastante descabellada, pero a medida que profundicemos en el tema y planteemos las distintas formas de como abordarlo, probablemente cambies de opinión.

Pair Programming

La Programación en Pareja o Pair Programming como su nombre indica es una técnica en la cual dos personas trabajan sobre el mismo código de software en un único terminal.

Para llevar esta práctica a cabo, debemos de identificar que rol va a desempeñar cada miembro de dicha pareja, pudiendo ser Driver o Navigator.

El driver es la persona que está escribiendo el código en ese momento y por tanto, es quien tiene que tener control del teclado y ratón, por ello este deberá centrarse en llevar a cabo la funcionalidad en base a lo que la pareja haya acordado previamente.

Por su parte el navigator, es el encargado ir supervisando, valorando y anotando posibles casuísticas que se puedan dar durante la codificación, y que el compañero no se esté percatando en ese momento o que no se hayan comentado previamente. Esto no quiere decir que se interrumpa el flujo de trabajo, sino que deberá tomar nota y llevarlo a consenso posteriormente. Su labor no es la de indicar errores sintácticos en pantalla, para eso ya está la herramienta de codificación.

Estos roles deben intercambiarse cada poco tiempo y en base a los propios factores de la pareja: Es probable que la tarea sea excesivamente compleja y se requiera un cambio de rol cada media hora, o que implique especial atención y se requiera de una concentración plena de tres horas para llevarla a cabo. Además influye el estado de ánimos de la persona en ese momento, su receptividad o afinidad a la tarea en cuestión.

A priori parece una labor sencilla, pero requiere de experiencia y factor humano el poder utilizar esta técnica para sacar todo el partido posible a trabajar en equipo.

Arquitectura hexagonal

Otro de los conceptos tratados en estos días y que es indispensable para poder utilizar TDD en nuestros desarrollos, es la Arquitectura Hexagonal, también conocida como patrón de puertos y adaptadores. A través de esta pretendemos desacoplar nuestro código de los distintos servicios externos con los que necesitaremos interactuar.

Extraído de codely.tv - Javier Ferrer

Para llevar a cabo esta arquitectura debemos tener claro que el consumo de los servicios de terceros o de otras arquitecturas se realizará a través de librerías de código.

Sin embargo, no las usaremos directamente, sino que crearemos un Wrapper o Envoltura para la misma, esto nos permitirá mockear la llamada a estos servicios desde el test (Spyes, Stubs, Mocks…), además de permitirnos fácilmente cambiar de herramienta sin que nuestro core se vea afectado.

El no usar este u otros tipos de patrones suele ser el principal problema a la hora de realizar TDD en Legacy Code, principalmente, porque la arquitectura, no está preparada para ser testeada.

KATAS

Son ejercicios que se realizan para practicar, son relativamente sencillos y es muy probable que en cuanto los veamos seamos capaces de diseñar una solución rápidamente en nuestras cabezas, pero esta no es su finalidad.

Lo que pretendemos con ellos es aplicar las lecciones aprendidas y mejorar nuestra forma de programar, además, combinándolas con el uso de TDD es muy probable que cada vez que realicemos la misma KATA, la solución no sea exactamente igual a la anterior.

Personalmente, nunca había trabajado con KATAS hasta esta semana, pero me ha parecido fascinante, han conseguido que vuelva a tener la percepción de estar jugando a la vez que aprendiendo que hacía tiempo no sentía.

Antes de empezar

Una vez nos hemos familiarizado con estos conceptos, vamos a plantear la forma de trabajo. La idea principal es realizar los KATAS propuestos posteriormente haciendo uso de TDD.

Para ello debemos identificar los casos de uso y el orden en el que los realizaremos antes de lanzarnos a picar código. Para ello se ha definido una prioridad en los casos que se indica en el siguiente cómic de Robert Martin, alias, Uncle Bob:

Debemos recordar que lo importante no es el resultado, sino entender el uso de TDD, adquirir cierta experiencia con él y poder aplicarlo en nuestro día a día.

Ante todo, no debemos sentir amor por el código, en ocasiones necesitaremos borrarlo, darle una vuelta de 360º a nuestro planteamiento y reescribirlo de nuevo, así que no debemos estar apegados a él, no se trata de nuestro hijo o de algo único y exclusivo, si está bien hecho (y con esto me refiero a que es simple y legible), podremos replicarlo fácilmente.

También es de vital importancia el uso de Git. Como consejo, deberíamos realizar un commit cuando el test que estemos elaborando se encuentre en rojo, y otro cuando se encuentre en verde, indicando así que parte de esa funcionalidad ya se encuentra operativa.

A continuación os dejo algunos de los KATAS que realizamos y el planteamiento de como los abordamos. Sería interesante que los intentaseis por vuestra cuenta, sin mirar nada de código y me comentaseis dudas que os pudieran abordar.

KATAS: String Calculator

Esta Kata consta de dos partes, la primera donde desarrollaremos la funcionalidad principal, y la segunda donde se realizará un cambio de requisitos en base a la anterior.

  • En primer lugar deberemos crear una función (no importa el lenguaje), la cual reciba una cadena de texto como parámetro, la cual represente un conjunto de números separados por comas y devuelva la suma de los mismos. En caso de que se pase una letra, se interpretará como un 0 en la suma.

Instintivamente lo primero que piensa alguien que no está acostumbrado a realizar TDD es el realizar la suma de todos los números directamente… pero esa no es nuestra finalidad.

Cuando aplicamos esta metodología lo primero que debemos es de detectar las distintas casuística que puedan ocurrir, y no dar por sentado ningún requisito que no se especifique.

Así pues nuestro planteamiento a la hora de resolverlo podría ser la siguiente:

/**
* TODO:
* param = '1' return 1
* param = 'a' return 0
* param = '' return 0
* param = '1, 1' return 2
* param = '1, a' return 1
* param = '1, 1, 1' return 3
*/

Para llevarlo a cabo, deberemos comenzar a resolver el test más simple, commitearlo como Red: Should sum one number y desarrollar la lógica para que nuestro test pase del estado Red a Green. Una vez realizado, podremos commitear el desarrollo como: Green: Should sum one number.

Debemos desarrollar los test y la lógica que permita llevar a cabo la funcionalidad de la forma más simple posible en ese momento, sin tener en cuenta lo que pueda venir posteriormente.

Una vez consideremos que nuestro código podría ser mejorable, llega el momento de refactorizarlo, crear métodos que envuelvan lógica y le den un nombre descriptivo a la misma, aplicar nombres de variables identificativas, sacar a métodos funcionalidad repetida, etc.

  • Una vez acabemos la funcionalidad, aplicaremos un cambio de requisitos, en esta ocasión queremos poder pasarle opcionalmente a la función cual será el carácter que indique la separación de los números. Para ello podremos aplicar el siguiente patrón //#;. Siendo // el indicador que permite identificar el comienzo del carácter especial, # el carácter que separará los números y ; el fin de la secuencia opcional. La secuencia completa que pasaríamos al método será la siguiente:
//#;1#2#3

A medida que avancemos y vayamos modificando el código podremos destacar que la tendencia de los if, es que estos se transformen de forma directa en un while, cosa que aplicando programación tradicional no suele ser usual. Sin embargo, nos permite mutar la funcionalidad de nuestro código de una manera mucho más rápida, simple y legible que un for.

KATAS: Partir Frases

En este Kata intentaremos simular la operación que realiza el editor de texto a la hora de aplicar saltos de línea. Para ello queremos desarrollar una función que reciba por parámetro dos valores, el primero será el texto que debemos dividir, mientras que el segundo, nos indicará el número de columnas que admitiremos. En el caso de que el corte de columna coincida con una letra, pero previamente exista un espacio en blanco, deberemos aplicar el salto de linea en el espacio en blanco y no cortar la palabra.

/**
* TODO:
* 'Hola', 5 return 'Hola'
* 'Hola', 2 return 'Ho\nla'
* 'Hola Mundo', 7 return 'Hola\nMundo'
*/

Es muy probable que a medida que avances en la elaboración de los test te percates de casos no contemplados anteriormente, como puede ser el caso de “¿Qué ocurre si indicamos que el número de columnas es 0?”.

En estos casos NO DEBEMOS TOMAR LA LEY POR NUESTRA CUENTA Y RIESGO, sino que deberemos consultar con el cliente, que desea que ocurra en estos casos previamente no contemplados.

En este caso que comentamos anteriormente, lo que haremos será que nuestro código devuelva un error, quedando por lo tanto nuestros casos a plasmar en los test como:

/**
* TODO:
* 'Hola', 5 return 'Hola'
* 'Hola', 2 return 'Ho\nla'
* 'Hola Mundo', 7 return 'Hola\nMundo'
* 'irrelevant', 0 throw error
*/

Este ejercicio es bastante complejo, así que os recomiendo que hagamos buen uso de los commits como indicamos anteriormente. En caso de que nos estanquemos, o tengamos la sensación de que estamos eligiendo una solución excesivamente compleja, borremos el código y volvamos a un punto estable.

Otro consejo interesante es que renombremos las variables para que representen que es exactamente lo que estamos haciendo, esto puede ayudarnos a ver más claro que es lo que debemos hacer.

¿Ya nos hemos peleado lo suficiente con él? Entonces, sigamos leyendo. Este es el típico caso de recursividad, donde la funcionalidad de partición de las palabras es lo bastante clara, pero a medida que avanzamos, nuestro código puede tomar soluciones válidas, pero excesivamente complejas. Si aplicamos los nombres de variables adecuados podremos ver con facilidad que con una simple llamada al método, todo quedará resuelto.

Cuando usamos TDD, es muy habitual que nuestro código tienda a mutar haciendo uso de recursividad antes que bucles, de hecho y contra todo lo que probablemente nos hayan enseñado, es aconsejable, ya que mejora la legibilidad y mantenibilidad del mismo.

En la web de Clean Coders podéis comprar el siguiente vídeo donde explican justamente este caso:

KATAS: Password Validator

En algún momento de nuestras vidas todos hemos odiado las restricciones impuestas a la hora de fijar una contraseña en un servicio, así que… vamos a replicarlo.

  • La contraseña debe contener al menos una letra mayúscula.
  • La contraseña debe contener al menos un número.
  • La contraseña debe contener al menos una letra minúscula.
  • La contraseña debe tener una longitud mínima de 6 caracteres.

En esta ocasión vamos a tratar de dar con los casos de uso, antes de plantearlos. Debemos tener en cuenta que los test que creemos, no deben ser manipulados una vez creados, porque esto les quitaría validez, no tiene sentido que modifiquemos los test para que nos devuelvan lo que queremos que nos devuelvan, y no lo que deberían.

WARNING: Esta Kata puede causar que si estás haciendo Pair Programming, se den situaciones tensan, haya conflicto de opiniones, y que el navigator quiera tomar el rol de driver a causa de las discrepancias. En esos momentos, es cuando de verdad vamos a poder practicar nuestras dotes humanas y como llevar el pair programming a buen puerto. Debemos de entender nuestro rol en ese momento y no presionar al driver, incluso si fuese necesario, debemos dejarlo avanzar con naturalidad para que se percate por si mismo que su opción puede no ser la correcta.

Después de esta advertencia… ¿Hemos notado algo raro? Si es así, enhorabuena, vamos por el buen camino. En esta Kata debemos de dar una vuela de tuerca a los requisitos.

En los casos anteriores hemos abordado primero los casos más simples, sin embargo, en este, debemos abordar primero el caso factible, que es que nuestra contraseña tenga de entrada todos los valores, y a partir de ahí verifiquemos que, a medida que incumplimos requisitos de la contraseña, nos devuelve que no es válida.

/*
* TODO:
* param = 'Abcde1' return true
* param = 'Abcdef' return false
* param = 'abcde1' return false
* param = 'ABCDE1' return false
* param = 'Abcde' return false
****/

Aplicando este orden de casos de uso, no tendremos problema a la hora de elaborar los test y la funcionalidad que deseamos.

No olvides aplicar los commits taggeando los estados Reda y Green al elaborar cada test y la funcionalidad correspondiente.

Conclusión

Aplicar TDD como metodología de desarrollo no implica únicamente realizar test para el código que vas a desarrollar, va mucho más allá. Es determinar las necesidades que existen y dar solución a ellas, de la forma que garanticemos que es justo lo que se desea.

Es cierto que mi tendencia natural en la frase anterior hubiese sido decir “óptimo”, pero esta palabra es demasiado ambigua, ¿qué es óptimo y que no lo es? ¿Tener una funcionalidad lo suficientemente genérica que de solución a todas las posibles casuísticas, con un nivel de complejidad y mantenimiento elevado? O por el contrario ¿tener una solución más rápida en el tiempo, que se ajuste a las necesidades y que sea lo suficientemente simple y natural como para que cualquier persona lo entienda?.

Yo desde luego, he decidido quedarme con la segunda, y he de reconocer que en muchas ocasiones he intentado de aplicar la primera, muy probablemente por ego de creer que esto me identificaría como buen profesional.

De hecho, Robert Martin explica esto de una mejor manera, a través del siguiente “mantra”:

As the tests get more specific, the code gets more generic. — Robert C. Martin, Transform Priority Premise.TPP.

Esto viene a decirnos que, cuantos más genérico tratemos que sea nuestro código, menos mantenible será. Sin embargo, si la premisa es que creemos unos test muy específicos y en base a ellos desarrollemos nuestra funcionalidad, esta terminará siendo más genérica y mantenible.

El tratar de conseguir directamente esta flexibilidad del código por nuestro propio riesgo, conseguirá generarnos muy probablemente más problemas que ventajas.

El TDD es una herramienta para aplicar este mantra, no es una obligación utilizarla, pero considero que es importante probarla, darle una oportunidad, invertir un poco de tiempo y ver si se adapta a nuestras necesidades y se adapta o mejora nuestra forma de trabajo.

Es probable que inicialmente sientas la sensación de que no sabes programar o de que todo el trabajo que has hecho durante tu recorrido está mal. Pero para nada es así, solo es necesario un poco de asimilación para que estas dudas se disipen.

Si quieres un último consejo, dedica todos los días media hora al comenzar a trabajar para revisar los commits del día anterior, en ver si entiendes el código. Es muy probable que decidas refactorizarlo, cambiando nombres de variables, extrayendo métodos… esto no solo ayudará a que tu código sea más legible, sino que te servirá para entrar en contexto de lo que estás haciendo.

El enlace a como resolvimos algunos de los Katas anteriores podéis encontrarlo en mi repositorio personal:

Para no extenderme mucho más he optado por omitir la parte de Refactor, Mocks, Stubs y Spyes, ya que implica una punto de vista más técnico y que podría dificultar el trasmitir los conceptos iniciales de TDD que quería plasmar.

Espero que este artículo te haya servido de ayuda y que te permita iniciarte en el mundo del TDD, o al menos que incentive tu curiosidad. Si así ha sido, agradecería cualquier comentario o valoración del artículo. También puedes dejarme tu feedback a través de mis redes sociales:

--

--