TDD o
Test-Driven Development (desarrollo dirigido por tests) es una práctica de programación que consiste
en escribir primero las pruebas (generalmente
unitarias), después escribir el código fuente que pase la prueba
satisfactoriamente y, por último, refactorizar el
código escrito. Con esta práctica se consigue entre otras cosas: un
código más robusto, más seguro, más mantenible y una mayor rapidez en el
desarrollo. En este post voy a centrarme solamente en como TDD afecta al diseño
de software, si queréis más información, hay una introducción bastante buena en
la Wikipedia y
Carlos Blé tiene disponible online un libro muy completo.
Antes
pensaba que TDD era una forma de programar que consistía en generar primero los
tests unitarios antes que la propia aplicación, con lo que conseguías
desarrollos de más calidad a costa de disminuir la productividad. Creo que es
la misma idea que tiene la mayoría de la gente que conoce por encima esta
práctica, pero que no se anima a utilizarla.
Sin
embargo, últimamente he estado profundizando un poco en TDD y me he dado cuenta
que esto no es cierto, TDD no es para hacer pruebas, es una práctica que envuelve el desarrollo en su conjunto,
especialmente el diseño de software. De hecho, algunos dicen que su última
letra, debería significar diseño y no desarrollo. Es decir, diseño orientado
por las pruebas.
TDD
fue creado por Kent Beck (quien
también inventó Extreme Programming y JUnit), y en esencia, es un proceso a
seguir, lo cual ya lo hace diferente a un simple enfoque de pruebas primero:
Este ciclo también se lo conoce como rojo (hacer que la prueba falle), verde (hacer que la prueba pase) y refactor. Aunque al principio pueda parecer muy parecido a
un enfoque de probar primero, al combinarlo con prácticas de
desarrollo ágil, TDD toma un enfoque mucho más amplio, y cambia su atención de las pruebas al diseño.
TDD
está mucho más relacionado con el diseño emergente que con las pruebas, de
hecho, que TDD genere una gran cantidad de pruebas es un efecto secundario
positivo, pero no es su propósito final.
El proceso de diseño de software, combinando TDD con
metodologías ágiles, sería el siguiente:
El
cliente escribe su historia de usuario.
Se
escriben junto con el cliente los criterios de aceptación de esta historia,
desglosándolos mucho para simplificarlos todo lo posible.
Se
escoge el criterio de aceptación más simple y se traduce en una prueba
unitaria.
Se
comprueba que esta prueba falla.
Se
escribe el código que hace pasar la prueba.
Se
ejecutan todas las pruebas automatizadas.
Se
refactoriza y se limpia el código.
Se
vuelven a pasar todas las pruebas automatizadas para comprobar que todo sigue
funcionando.
Volvemos
al punto 3 con los criterios de aceptación que falten y repetimos el ciclo una
y otra vez hasta completar nuestra aplicación.
Vamos
con un ejemplo práctico de este ciclo:
Supongamos
que el cliente nos pide que desarrollemos una calculadora que sume números (es
lo primero que se me ha ocurrido).
Acordamos
con el cliente que el criterio de aceptación sería que si introduces en la
calculadora dos números y le das a la operación de .suma, la calculadora te
muestra el resultado de la suma en la pantalla.
Partiendo
de este criterio, comenzamos a definir el funcionamiento del algoritmo de suma
y convertimos el criterio de aceptación en una prueba concreta, por ejemplo, un
algoritmo que si introduces un 3 y un 5 te devuelve un 8:
public void testSuma() {
assertEquals(8,
Calculadora.suma(3,5));
}
Este
punto es para mí el más importante del TDD y que supone un cambio de
mentalidad, primero escribo cómo debe funcionar mi programa y después, una vez
lo tengo claro, paso a codificarlo.
Al
escribir el test estoy diseñando cómo va a funcionar el software, pienso que
para cubrir la prueba voy a necesitar una clase Calculadora con una función que
se llame Suma y que tenga dos parámetros.
Esta
clase todavía no existe pero cuando la cree, ya sé cómo va a funcionar. Este
caso es muy trivial, pero muchas veces no sabemos exactamente qué clases hacer
o qué métodos ponerle exactamente.
Es
más, a menudo perdemos el tiempo haciendo métodos y clases que pensamos
que luego serán útiles, cuando la cruda realidad es que muchas veces no se van
a usar nunca. Con TDD sólo hacemos lo que realmente necesitamos en ese momento.
Realmente
es la forma natural de pensar, primero pensamos en «qué»
queremos hacer y después pasamos al «cómo», la diferencia es que con TDD
el test ya queda escrito y se ejecutará cada vez que compilamos nuestro
programa.
Por
supuesto, si intentamos pasar este test nos dará un error, porque la clase
Calculadora aún no existe.
Ahora
pasamos a escribir el código de la clase, es fácil porque ya sabemos
exactamente cómo se va a comportar:
public class Calculadora {
public
static int suma (int a, int b) {
int c =
a + b;
return
c;
}
}
Ahora
ejecutamos la prueba y ya tenemos el código funcionado con la prueba pasada.
Una
vez todo esté funcionando, pasamos a refactorizar y a eliminar código
duplicado, este ejemplo es extremadamente sencillo, y en un caso real no
haríamos tantos pasos para algo tan evidente, pero el código mejorado podría
ser por ejemplo:
public class Calculadora {
public
static int suma (int a, int b) {
return
a+b;
}
}
En
ejemplos más complejos, según vayamos escribiendo más test, deberíamos buscar
código duplicado y agruparlo en funciones o utilizar la herencia o el
polimorfismo.
Es
importante pasar todos los test después de refactorizar por si nos hemos
cargado algo.
Ahora
deberíamos volver al punto 3 con tests más complicados y repetir el proceso,
por ejemplo, podíamos pasar a que el algoritmo admita sumar números decimales,
etc.
Esta
forma de trabajar es también muy buena para entender el código. Sabemos que
la calidad del diseño de un software está también
relacionada con el conocimiento del equipo de desarrollo en relación al dominio
en cuestión. En este sentido, las pruebas son una muy buena
forma de entender el código y su funcionamiento, muchas veces incluso mejor que
la documentación.
También
hay que decir que no todo es perfecto en TDD, cuando llegue el momento de crear
un test sobre la interfaz de la calculadora la cosa se complica. Los puntos flojos que veo en TDD son:
Hay
que utilizarlo y entenderlo bien para que sea realmente productivo, te ayuda a
centrarte en lo importante y a no sobrediseñar, pero es importante saber
refactorizar el código según vaya evolucionando para que sea consistente.
Pruebas
sobre interfaces gráficas. Aunque hay soluciones parciales propuestas, para mí
TDD solo funciona en la capa de negocio, no encaja con interfaces visuales.
Bases de datos. Hacer pruebas
de código que trabaja con base de datos es complejo porque requiere generar
unos datos conocidos antes de hacer las pruebas y verificar que el contenido de
la base de datos es el esperado después de la prueba. Los objetos simulados
(MockObjects) son otra opción, pero personalmente creo que se pierde tiempo con
esto.
Comments
Post a Comment