En este tema practicaremos con mayor detalle el ciclo central de TDD: rojo, verde y refactor. El objetivo no es escribir muchas pruebas de golpe, sino aprender a avanzar con una prueba pequeña, una implementación mínima y una mejora controlada del código.
A partir de este tema consideramos que el entorno virtual ya fue creado, activado y que pytest ya está instalado. Por eso nos concentraremos directamente en el trabajo de desarrollo.
Crearemos una pequeña calculadora con operaciones básicas. Por ahora no intentaremos resolver toda la calculadora completa. Iremos agregando comportamiento mediante pruebas.
Usaremos estos archivos:
calculadora.py
test_calculadora.py
Por simplicidad los mantendremos en la misma carpeta del ejercicio. Más adelante organizaremos proyectos con carpetas separadas para código y pruebas.
El primer comportamiento será sumar dos números. En TDD conviene empezar por algo concreto y fácil de verificar.
Este ejemplo será nuestra primera especificación ejecutable.
Primero escribimos la prueba. Todavía no creamos la función sumar.
Archivo a crear: test_calculadora.py
from calculadora import sumar
def test_sumar_dos_numeros_positivos():
resultado = sumar(2, 3)
assert resultado == 5
Ejecutamos la prueba:
python -m pytest
La prueba debe fallar. Ese fallo indica que la prueba está pidiendo un comportamiento que el sistema todavía no tiene.
Un error posible es:
ModuleNotFoundError: No module named 'calculadora'
El mensaje indica que falta el archivo calculadora.py. El siguiente paso mínimo no es implementar una calculadora completa, sino crear lo necesario para avanzar.
Creamos el archivo de producción con la solución más simple que permite pasar la prueba.
Archivo a crear: calculadora.py
def sumar(a, b):
return 5
Ejecutamos nuevamente:
python -m pytest
La prueba pasa. Aunque la implementación parece demasiado simple, cumple el comportamiento que la prueba actual exige.
Al comenzar con TDD puede resultar extraño devolver directamente 5. Sin embargo, esta práctica enseña una regla importante: no escribir código antes de que una prueba lo justifique.
La implementación todavía no es general. La siguiente prueba nos obligará a mejorarla.
Agregamos otra prueba para evitar que la función solo sirva para el primer caso.
Archivo a modificar: test_calculadora.py
from calculadora import sumar
def test_sumar_dos_numeros_positivos():
resultado = sumar(2, 3)
assert resultado == 5
def test_sumar_otros_dos_numeros_positivos():
resultado = sumar(4, 6)
assert resultado == 10
Ejecutamos:
python -m pytest
Ahora la segunda prueba falla, porque sumar sigue devolviendo siempre 5.
La nueva prueba justifica cambiar la implementación para que use los parámetros.
Archivo a modificar: calculadora.py
def sumar(a, b):
return a + b
Ejecutamos nuevamente:
python -m pytest
Si ambas pruebas pasan, estamos otra vez en verde.
En esta etapa preguntamos si el código necesita una mejora interna. La función sumar es clara, breve y no tiene duplicación. En este caso no hace falta modificarla.
Ahora agregaremos una operación nueva. Empezamos otra vez con una prueba fallida.
Archivo a modificar: test_calculadora.py
from calculadora import restar, sumar
def test_sumar_dos_numeros_positivos():
resultado = sumar(2, 3)
assert resultado == 5
def test_sumar_otros_dos_numeros_positivos():
resultado = sumar(4, 6)
assert resultado == 10
def test_restar_dos_numeros():
resultado = restar(10, 4)
assert resultado == 6
Ejecutamos python -m pytest. La prueba debe fallar porque restar todavía no existe.
Agregamos la función necesaria.
Archivo a modificar: calculadora.py
def sumar(a, b):
return a + b
def restar(a, b):
return a - b
Ejecutamos:
python -m pytest
Si todas las pruebas pasan, el nuevo comportamiento quedó incorporado.
El archivo de pruebas tiene nombres claros, pero empieza a crecer. Todavía no hay una duplicación problemática. Las pruebas son repetitivas en una medida aceptable porque cada una describe un comportamiento concreto.
En TDD no eliminamos toda repetición de inmediato. Primero distinguimos entre repetición útil, que mejora la lectura de los ejemplos, y duplicación que vuelve difícil cambiar el código.
Aplicamos el mismo ritmo para una tercera operación.
Archivo a modificar: test_calculadora.py
from calculadora import multiplicar, restar, sumar
def test_sumar_dos_numeros_positivos():
assert sumar(2, 3) == 5
def test_sumar_otros_dos_numeros_positivos():
assert sumar(4, 6) == 10
def test_restar_dos_numeros():
assert restar(10, 4) == 6
def test_multiplicar_dos_numeros():
assert multiplicar(3, 4) == 12
En este ejemplo además simplificamos las pruebas usando el resultado directamente en el assert. Es una pequeña refactorización del archivo de pruebas.
Agregamos la función que falta.
Archivo a modificar: calculadora.py
def sumar(a, b):
return a + b
def restar(a, b):
return a - b
def multiplicar(a, b):
return a * b
Ejecutamos python -m pytest y confirmamos que toda la suite esté en verde.
Podemos mejorar las pruebas de suma usando parametrización de pytest. Esto expresa que varios ejemplos comprueban el mismo comportamiento.
Archivo a modificar: test_calculadora.py
import pytest
from calculadora import multiplicar, restar, sumar
@pytest.mark.parametrize(
"a, b, esperado",
[
(2, 3, 5),
(4, 6, 10),
],
)
def test_sumar_dos_numeros(a, b, esperado):
assert sumar(a, b) == esperado
def test_restar_dos_numeros():
assert restar(10, 4) == 6
def test_multiplicar_dos_numeros():
assert multiplicar(3, 4) == 12
Después de refactorizar las pruebas, ejecutamos python -m pytest. Si todo pasa, cambiamos la forma de las pruebas sin alterar el comportamiento comprobado.
Durante el refactor no deberíamos agregar comportamiento nuevo. La idea es mejorar la estructura, los nombres o la duplicación manteniendo las pruebas en verde.
Un error frecuente es escribir una prueba, implementar varias funciones, cambiar nombres, agregar validaciones y modificar pruebas al mismo tiempo. Eso dificulta saber qué cambio produjo un fallo.
El ciclo rojo, verde y refactor evita ese problema porque separa las decisiones:
Agrega la operación dividir aplicando el ciclo completo:
dividir(10, 2) y espera 5.python -m pytest y verifica que la prueba falle.Antes de continuar, verifica lo siguiente:
python -m pytest después de cada cambio importante.En este tema aplicamos varias veces el ciclo rojo, verde y refactor. Primero escribimos una prueba que falla, luego implementamos lo mínimo para pasarla y finalmente revisamos si el código o las pruebas necesitaban una mejora.
Este ritmo es la base de TDD. En el próximo tema prepararemos un proyecto mínimo más ordenado para practicar el ciclo con una estructura que se parezca más a la de un proyecto real.