Test-Driven Development

Introducción

por Javier Treviño Saldaña

Test-driven development

o "desarrollo guiado por pruebas", es una técnica que ayuda a crear software confiable, legible y simple de mantener. Aunque estas características no están garantizadas porque dependen del entendimiento y la habilidad de cada quien, es lo más cercano que conozco a una "silver bullet" para lograrlo.

La técnica consiste en escribir software para probar tu software. Por ejemplo, supongamos que vamos a crear una función "reverse" que acepta una lista como input, y regresa como output esa misma lista con sus elementos en reversa.

list = [1, 2, 3, 4, 5]
reverse(list)
# => [5, 4, 3, 2, 1]

Con este objetivo en mente, y empezando de cero, podemos implementar la función gradualmente aplicando TDD. Nuestra primera prueba debe ser simple, por ejemplo:

test "reversing an empty list" {
  assert reverse([]) == []
}

Esta primera prueba sencilla es útil para asegurarnos de que nuestro ambiente para correr pruebas está configurado correctamente. Al ejecutar la prueba esperamos ver un error útil, no un error de compilación. Además esta primera prueba nos ayuda a preguntarnos cómo queremos llamar esta función desde el resto de nuestro sistema ("reverse"), y los parámetros que nos gustaría que acepte (una lista).

Sin hacer nada más, al ejecutar esta prueba en nuestra terminal esperamos que falle, pues no hemos definido la función reverse.

$ test

Running 1 test...
FAILURE

Exception: "reverse" is not defined

Implementemos lo mínimo necesario para pasar la prueba:

function reverse(list) {
  return []
}
$ test

Running 1 test...
OK

Ya hemos hecho progreso importante; tenemos una prueba que garantiza que un caso válido de nuestra función "reverse" funciona. Sigamos con un caso ligeramente más complicado al anterior:

test "reversing a list with one element" {
  assert reverse([7]) == [7]
}
$ test

Running 2 tests...
1 OK, 1 FAILURE

Expected reverse([7]) to equal [7], but was []

Hacemos los cambios necesarios para pasar la prueba:

function reverse(list) {
  return list
}
$ test

Running 2 tests...
OK

De esta manera podemos agregar casos cada vez más complejos, y dar pasos firmes sabiendo que mientras las pruebas pasen, nuestro código funciona.

Una vez que terminemos de implementar nuestra función, tendremos una suite de pruebas que:

  • Garantiza que nuestro código "funciona"
  • Plasma nuestras expectativas, es decir, nuestra definición de "funciona"
  • Protege contra cambios futuros, al alertar si un cambio rompe nuestras expectativas
  • Sirve como documentación, y siempre se mantiene actualizada si la función cambia