4. Primera prueba fallida: convertir un requisito en una especificación ejecutable

4.1 Objetivo del tema

En este tema practicaremos una parte esencial de TDD: escribir la primera prueba fallida a partir de un requisito. La prueba no será un agregado posterior, sino la primera forma concreta de expresar qué debe hacer el programa.

Nos concentraremos en la etapa roja. Es decir, escribiremos una prueba, la ejecutaremos y verificaremos que falle por la razón esperada. La implementación mínima para pasar esa prueba quedará para el próximo tema.

Objetivo práctico: transformar una necesidad escrita en lenguaje natural en una prueba automatizada clara, ejecutable y fallida.

4.2 Qué es una especificación ejecutable

Una especificación ejecutable es una descripción del comportamiento esperado que la computadora puede verificar. En TDD, una prueba automatizada cumple ese rol.

En lugar de dejar el requisito solo como texto, lo convertimos en un ejemplo concreto con entrada, acción y resultado esperado. Al ejecutar la prueba, sabemos si el programa cumple o no esa parte del comportamiento.

4.3 Requisito de trabajo

Trabajaremos con un validador de contraseñas. El primer requisito será deliberadamente pequeño:

Una contraseña es válida si tiene al menos 8 caracteres.

Este requisito todavía no habla de mayúsculas, números, símbolos ni reglas de seguridad avanzadas. En TDD empezamos con un comportamiento pequeño y luego agregamos nuevos requisitos mediante nuevas pruebas.

4.4 Identificar el comportamiento observable

Antes de escribir código, debemos decidir qué queremos observar desde afuera. En este caso queremos llamar a una función y obtener una respuesta booleana.

  • Entrada: una cadena de texto con la contraseña.
  • Acción: validar la contraseña.
  • Salida esperada: True si cumple el requisito.

La función podría llamarse es_password_valido. Ese nombre comunica una pregunta cuya respuesta será verdadera o falsa.

4.5 Elegir el primer ejemplo

El primer ejemplo debe ser simple y directo. Una contraseña con 8 caracteres exactos sirve para comprobar el límite mínimo permitido.

Ejemplo inicial: "abcdefgh" tiene 8 caracteres, por lo tanto debe ser válida.

Elegir un ejemplo exacto ayuda a evitar ambigüedades. No estamos diciendo todavía qué pasa con 7 caracteres ni con una cadena vacía. Eso lo veremos con otras pruebas.

4.6 Preparar los archivos

Usaremos una estructura mínima similar a la del tema anterior:

password-tdd/
|-- src/
|   `-- seguridad/
|       `-- __init__.py
`-- tests/

El archivo de prueba estará en tests. El código de producción vivirá dentro del paquete seguridad.

4.7 Crear la prueba antes del código

Ahora escribimos la prueba. Todavía no creamos password.py ni la función es_password_valido.

Archivo a crear: tests/test_password.py

from seguridad.password import es_password_valido


def test_password_con_ocho_caracteres_es_valido():
    resultado = es_password_valido("abcdefgh")

    assert resultado is True

Esta prueba expresa el requisito como un ejemplo ejecutable.

4.8 Ejecutar la prueba

Ejecutamos la suite:

python -m pytest

La prueba debe fallar. En TDD eso no significa que hicimos algo mal; significa que acabamos de describir un comportamiento que todavía no existe.

4.9 Leer el fallo esperado

Un fallo posible es:

ModuleNotFoundError: No module named 'seguridad.password'

Este fallo es razonable porque la prueba intenta importar un módulo que todavía no existe. Estamos en rojo por una causa esperada.

Una buena prueba roja falla por el comportamiento que falta, no por un error accidental de escritura, ubicación o configuración.

4.10 Qué información nos da esta prueba

La prueba ya tomó varias decisiones de diseño:

  • Existirá un módulo llamado seguridad.password.
  • Existirá una función llamada es_password_valido.
  • La función recibirá una cadena.
  • La función devolverá un valor booleano.
  • Una contraseña con 8 caracteres debe devolver True.

Estas decisiones aparecen antes de implementar la función. Por eso decimos que la prueba guía el diseño.

4.11 Mejorar el nombre de la prueba

El nombre test_password_con_ocho_caracteres_es_valido es claro, pero podemos hacerlo un poco más cercano al requisito:

Archivo a modificar: tests/test_password.py

from seguridad.password import es_password_valido


def test_password_es_valido_si_tiene_al_menos_ocho_caracteres():
    resultado = es_password_valido("abcdefgh")

    assert resultado is True

Un buen nombre de prueba ayuda a entender el comportamiento incluso antes de leer el cuerpo de la prueba.

4.12 Patrón Arrange, Act, Assert

Podemos escribir la prueba separando preparación, acción y verificación:

Archivo a modificar: tests/test_password.py

from seguridad.password import es_password_valido


def test_password_es_valido_si_tiene_al_menos_ocho_caracteres():
    password = "abcdefgh"

    resultado = es_password_valido(password)

    assert resultado is True

Primero preparamos el dato, luego ejecutamos la acción y finalmente verificamos el resultado.

4.13 Cuándo conviene usar variables

En pruebas muy pequeñas podríamos escribir todo en una línea:

assert es_password_valido("abcdefgh") is True

Pero al aprender TDD es útil separar los pasos. Las variables hacen visible qué dato estamos usando y qué resultado esperamos obtener.

4.14 No escribir todavía la implementación

En este tema nos detenemos en la etapa roja. Aunque ya sepamos cómo implementar la función, todavía no lo haremos.

Esta pausa es importante: antes de pasar a verde debemos estar seguros de que la prueba expresa correctamente el requisito y que el fallo es el esperado.

4.15 Agregar un segundo ejemplo negativo

El requisito dice “al menos 8 caracteres”. Para completar la idea, podemos escribir una prueba para una contraseña demasiado corta.

Archivo a modificar: tests/test_password.py

from seguridad.password import es_password_valido


def test_password_es_valido_si_tiene_al_menos_ocho_caracteres():
    password = "abcdefgh"

    resultado = es_password_valido(password)

    assert resultado is True


def test_password_es_invalido_si_tiene_menos_de_ocho_caracteres():
    password = "abc"

    resultado = es_password_valido(password)

    assert resultado is False

Esta segunda prueba todavía fallará por la misma razón: la función no existe. En el próximo tema implementaremos lo mínimo para empezar a satisfacer estos ejemplos.

4.16 Evitar requisitos demasiado grandes

Un requisito como “validar una contraseña segura” es demasiado amplio para una primera prueba. Puede incluir longitud, mayúsculas, minúsculas, números, símbolos, palabras prohibidas y otras reglas.

En TDD conviene dividir ese requisito en comportamientos pequeños. Por ejemplo:

  • Debe tener al menos 8 caracteres.
  • Debe contener al menos un número.
  • Debe contener al menos una letra mayúscula.
  • No debe aceptar espacios al inicio o al final.

Cada regla puede nacer con una prueba roja propia.

4.17 Qué revisar antes de pasar a verde

Antes de implementar, revisa estas preguntas:

  • ¿La prueba describe un comportamiento concreto?
  • ¿El nombre de la prueba explica el requisito?
  • ¿El dato de entrada es fácil de entender?
  • ¿El resultado esperado es claro?
  • ¿La prueba falla por una razón esperada?

Si alguna respuesta es dudosa, conviene mejorar la prueba antes de escribir producción.

4.18 Errores frecuentes

  • Escribir la implementación antes de la prueba: rompe el flujo de TDD porque ya no vemos la prueba roja.
  • Probar demasiadas reglas juntas: dificulta saber qué comportamiento está fallando.
  • Usar nombres genéricos: una prueba llamada test_password no explica qué se espera.
  • No ejecutar la prueba antes de implementar: impide comprobar que la prueba realmente detecta el comportamiento faltante.
  • Aceptar cualquier fallo: una prueba roja debe fallar por la razón esperada, no por un error accidental.

4.19 Ejercicio propuesto

Escribe una prueba roja para el siguiente requisito, pero no implementes todavía el código:

Una contraseña es inválida si está vacía.

La prueba debería usar es_password_valido("") y esperar False. Después ejecútala con python -m pytest y verifica que siga fallando por una razón esperada.

4.20 Lista de verificación

Antes de continuar, verifica lo siguiente:

  • Convertiste un requisito en un ejemplo concreto.
  • Escribiste la prueba antes de crear la implementación.
  • La prueba tiene un nombre que describe comportamiento.
  • Usaste una entrada y un resultado esperado claros.
  • Ejecutaste python -m pytest.
  • La prueba falló por una razón esperada.
  • No implementaste la función todavía.

4.21 Conclusión

En este tema convertimos un requisito en una especificación ejecutable. Escribimos una prueba antes del código, elegimos ejemplos concretos, leímos el fallo y revisamos si la prueba comunicaba bien el comportamiento esperado.

En el próximo tema usaremos esta prueba roja como punto de partida para escribir el código mínimo necesario y pasar a la etapa verde sin adelantar diseño innecesario.