2. El ciclo rojo, verde y refactor aplicado con Python y pytest

2.1 Objetivo del tema

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.

Objetivo práctico: desarrollar una calculadora simple aplicando varios ciclos rojo, verde y refactor con Python y pytest.

2.2 Punto de partida

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.

2.3 Elegir un comportamiento pequeño

El primer comportamiento será sumar dos números. En TDD conviene empezar por algo concreto y fácil de verificar.

Comportamiento inicial: si sumamos 2 y 3, el resultado debe ser 5.

Este ejemplo será nuestra primera especificación ejecutable.

2.4 Rojo: escribir la primera prueba

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.

2.5 Leer el fallo antes de escribir código

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.

2.6 Verde: escribir lo mínimo

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.

2.7 Por qué aceptar una solución tan simple

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.

2.8 Rojo otra vez: agregar un segundo ejemplo

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.

2.9 Verde: generalizar lo mínimo necesario

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.

2.10 Refactor: revisar si hace falta mejorar

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.

Refactorizar no significa cambiar por cambiar. Si el código ya es simple y claro, la mejor decisión puede ser dejarlo como está.

2.11 Nuevo comportamiento: restar dos números

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.

2.12 Verde: implementar resta

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.

2.13 Refactor: observar nombres y duplicación

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.

2.14 Tercer ciclo: multiplicar

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.

2.15 Implementar multiplicación

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.

Ejecución exitosa de la suite con suma, resta y multiplicación

2.16 Refactorizar las pruebas sin romper comportamiento

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.

2.17 La regla de oro del refactor

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.

  • Si una prueba está fallando, primero hay que volver a verde.
  • Si queremos agregar comportamiento, escribimos una nueva prueba roja.
  • Si solo queremos mejorar el diseño, no cambiamos los resultados esperados.

2.18 Error común: mezclar etapas

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:

  • En rojo decidimos qué comportamiento falta.
  • En verde decidimos cuál es el código mínimo para cumplirlo.
  • En refactor decidimos cómo mejorar el diseño sin cambiar el comportamiento.

2.19 Ejercicio propuesto

Agrega la operación dividir aplicando el ciclo completo:

  • Escribe primero una prueba para dividir(10, 2) y espera 5.
  • Ejecuta python -m pytest y verifica que la prueba falle.
  • Implementa el código mínimo para pasar la prueba.
  • Ejecuta nuevamente la suite.
  • Refactoriza solo si encuentras una mejora clara.

2.20 Lista de verificación

Antes de continuar, verifica lo siguiente:

  • Escribiste una prueba antes de cada nuevo comportamiento.
  • Viste al menos una prueba fallar por la razón esperada.
  • Implementaste el código mínimo para volver a verde.
  • Ejecutaste python -m pytest después de cada cambio importante.
  • Refactorizaste código o pruebas solo cuando la suite estaba en verde.
  • Comprendes que el ciclo se repite muchas veces en pasos pequeños.

2.21 Conclusión

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.