41. Buenas prácticas para mantener una suite rápida y clara

41.1 Introducción

Una suite de pruebas unitarias no se mantiene útil por accidente. Al principio puede ser pequeña, rápida y fácil de entender, pero con el tiempo aparecen más casos, más datos, más dependencias y más decisiones de diseño.

Si no cuidamos la suite, puede volverse lenta, frágil o confusa. Cuando eso ocurre, el equipo empieza a ejecutarla menos, a ignorar fallas intermitentes o a modificar pruebas sin entender qué protegían.

Este tema reúne buenas prácticas para mantener pruebas unitarias rápidas, claras y confiables durante la vida del proyecto.

41.2 Qué significa una suite rápida y clara

Una suite rápida se ejecuta en poco tiempo y permite recibir retroalimentación frecuente. Una suite clara permite entender qué comportamiento falló y por qué importa.

Una buena suite no solo dice que algo falló: ayuda a localizar el comportamiento que dejó de cumplirse.

La velocidad y la claridad trabajan juntas. Si las pruebas son rápidas pero incomprensibles, el diagnóstico será costoso. Si son claras pero muy lentas, se ejecutarán poco. El objetivo es sostener ambas cualidades.

41.3 Mantener pruebas pequeñas

Una prueba pequeña tiene una intención principal, una preparación acotada y una verificación concreta. Esto facilita leerla, entenderla y corregirla cuando falla.

def test_cliente_con_1000_puntos_es_vip():
    cliente = Cliente(puntos=1000)

    assert cliente.es_vip() is True

La prueba anterior es pequeña porque muestra una regla específica. No intenta crear un flujo completo de compras, registrar pagos ni consultar una base de datos.

41.4 Nombrar según comportamiento

Los nombres deben describir el comportamiento esperado. Esto ayuda a interpretar fallas sin abrir inmediatamente el cuerpo de la prueba.

def test_retirar_mas_que_el_saldo_genera_error():
    cuenta = Cuenta(saldo=500)

    with pytest.raises(ValueError):
        cuenta.retirar(800)

El nombre comunica la regla. Si esta prueba falla, sabemos que el problema está relacionado con el rechazo de retiros superiores al saldo.

41.5 Usar datos simples y representativos

Los datos deben explicar el caso. Cuando sea posible, conviene elegir valores fáciles de verificar mentalmente.

def test_descuento_del_20_por_ciento():
    assert aplicar_descuento(1000, 20) == 800

El valor 1000 hace que el resultado esperado sea evidente. Si la prueba necesita un dato complejo, debe haber una razón clara: un caso límite, una regla especial o una regresión previa.

41.6 Probar comportamiento observable

Una suite clara se enfoca en lo que la unidad promete hacer, no en cada detalle interno de cómo lo hace. Esto permite refactorizar el código productivo sin romper pruebas innecesariamente.

def test_agregar_producto_incrementa_total():
    carrito = Carrito()

    carrito.agregar("mouse", 1000)

    assert carrito.total() == 1000

La prueba no revisa la lista interna ni el nombre de variables privadas. Verifica el resultado que importa para quien usa la clase.

41.7 Evitar recursos externos en pruebas unitarias

Para mantener velocidad y determinismo, las pruebas unitarias deben evitar bases de datos reales, APIs externas, red y archivos persistentes cuando no son necesarios.

Si una regla puede probarse con datos en memoria, esa suele ser la mejor opción:

def test_producto_sin_stock_no_puede_venderse():
    producto = Producto(stock=0, activo=True)

    assert producto.puede_venderse(1) is False

Las pruebas que verifican conexión con base de datos o servicios externos son valiosas, pero pertenecen a otro nivel de testing.

41.8 Mantener independencia entre pruebas

Cada prueba debe poder ejecutarse sola, en cualquier orden y con el mismo resultado. Para eso, debe preparar su propio estado o recibirlo desde una fixture limpia.

@pytest.fixture
def carrito_vacio():
    return Carrito()


def test_carrito_inicia_vacio(carrito_vacio):
    assert carrito_vacio.cantidad() == 0


def test_agregar_producto_incrementa_cantidad(carrito_vacio):
    carrito_vacio.agregar("mouse", 1000)

    assert carrito_vacio.cantidad() == 1

pytest crea una instancia nueva de la fixture por prueba, salvo que se indique otro alcance. Esto ayuda a evitar contaminación de estado.

41.9 Controlar el tiempo y la aleatoriedad

Las pruebas deben ser determinísticas. Si una unidad depende de la fecha actual o de números aleatorios, conviene inyectar esos valores o usar dobles controlados.

def test_cupon_vencido_no_es_valido():
    hoy = date(2026, 5, 10)
    cupon = Cupon(fecha_vencimiento=date(2026, 5, 9))

    assert cupon.es_valido_en(hoy) is False

Pasar la fecha explícitamente evita que la prueba cambie de resultado según el día de ejecución.

41.10 Organizar archivos por unidad o comportamiento

La organización de archivos debe ayudar a encontrar pruebas. Una convención simple es crear archivos de prueba que correspondan a módulos, clases o áreas de comportamiento.

tests/
  test_carrito.py
  test_cuenta.py
  test_validaciones_usuario.py
  test_reglas_pedido.py

No existe una única estructura correcta. Lo importante es que el equipo pueda ubicar rápidamente dónde están las pruebas relacionadas con una unidad o regla.

41.11 Separar pruebas unitarias de otros niveles

Una causa frecuente de suites lentas es mezclar pruebas unitarias con integración, end-to-end o pruebas que requieren infraestructura externa.

Conviene separarlas por carpeta, marca o configuración:

tests/
  unit/
  integration/
  e2e/

Así podemos ejecutar pruebas unitarias con frecuencia y reservar las pruebas más costosas para momentos adecuados del flujo de desarrollo.

41.12 Ejecutar pruebas frecuentemente

Una suite rápida vale más cuando se ejecuta seguido. Durante el desarrollo, conviene correr las pruebas relacionadas con el cambio actual y luego una suite más amplia antes de integrar.

pytest tests/unit/test_carrito.py

La ejecución frecuente reduce la distancia entre el cambio que introdujo un problema y el momento en que lo detectamos.

41.13 Usar fixtures con criterio

Las fixtures ayudan a reducir preparación repetida, pero deben mantenerse pequeñas y explícitas. Una fixture debe describir claramente el estado que entrega.

@pytest.fixture
def cuenta_con_saldo_500():
    return Cuenta(saldo=500)

El nombre comunica el dato relevante. En cambio, una fixture llamada setup o contexto obliga a buscar su contenido para entender la prueba.

41.14 Parametrizar sin ocultar intención

Las pruebas parametrizadas reducen repetición cuando varios casos tienen la misma estructura. La tabla de datos debe ser pequeña y significativa.

import pytest


@pytest.mark.parametrize("edad, esperado", [
    (17, False),
    (18, True),
    (19, True),
])
def test_puede_registrarse_segun_edad(edad, esperado):
    assert puede_registrarse(edad) is esperado

Este uso es claro porque todos los casos giran alrededor del mismo límite. Si cada fila representa una regla distinta, puede ser mejor escribir pruebas separadas.

41.15 Mantener aserciones expresivas

La aserción debe mostrar qué resultado importa. Una aserción demasiado genérica puede pasar aunque el comportamiento correcto no esté garantizado.

def test_productos_disponibles():
    productos = obtener_productos_disponibles()

    assert "teclado" in productos

Esta aserción es más informativa que comprobar solamente que la lista no está vacía, siempre que el comportamiento esperado sea incluir el producto con stock.

41.16 Revisar pruebas junto con el código productivo

Cuando cambia una funcionalidad, también debe revisarse si las pruebas siguen expresando el comportamiento correcto. Las pruebas son parte del código del proyecto.

En una revisión conviene mirar:

  • Si los nuevos comportamientos tienen pruebas.
  • Si los casos límite importantes están cubiertos.
  • Si las pruebas existentes siguen describiendo reglas vigentes.
  • Si se agregaron dependencias externas innecesarias.
  • Si los nombres explican qué se verifica.

41.17 Eliminar o corregir pruebas sin valor

Una prueba que no verifica nada importante puede confundir más de lo que ayuda. Antes de eliminarla, conviene entender si protegía algún comportamiento que debería conservarse.

Algunas pruebas candidatas a revisión son:

  • Pruebas sin aserciones.
  • Pruebas que solo cubren líneas sin comprobar resultados.
  • Pruebas duplicadas que verifican exactamente lo mismo.
  • Pruebas acopladas a detalles internos irrelevantes.
  • Pruebas que fallan de forma intermitente y nadie investiga.

La meta no es tener más pruebas, sino tener mejores señales.

41.18 Investigar fallas intermitentes

Una falla intermitente no debe ignorarse. Si el equipo se acostumbra a repetir la suite hasta que pase, pierde confianza en todas las pruebas.

Cuando una prueba falla a veces, conviene revisar:

  • Estado compartido entre pruebas.
  • Dependencia de fecha, hora o zona horaria.
  • Uso de aleatoriedad sin control.
  • Orden no garantizado de resultados.
  • Recursos externos o concurrencia.

Una suite clara debe producir resultados confiables. Una falla intermitente es una deuda técnica real.

41.19 Medir duración de pruebas

Cuando una suite empieza a tardar demasiado, conviene identificar las pruebas más lentas. pytest permite mostrar duraciones:

pytest --durations=10

Las pruebas más lentas suelen revelar dependencias externas, preparación costosa o casos que quizá pertenecen a otro nivel de testing.

No hace falta optimizar prematuramente cada milisegundo, pero sí conviene evitar que la suite unitaria pierda su velocidad característica.

41.20 Mantener una convención de estilo

Una suite es más fácil de leer cuando las pruebas siguen convenciones similares: nombres, estructura, ubicación de fixtures, uso de parametrización y estilo de aserciones.

Por ejemplo, el equipo puede acordar:

  • Usar nombres que describan comportamiento.
  • Separar preparación, ejecución y verificación con líneas en blanco.
  • Preferir datos simples salvo que el caso requiera otra cosa.
  • Usar fixtures pequeñas y con nombres descriptivos.
  • Separar pruebas unitarias de pruebas de integración.

La consistencia reduce el esfuerzo de leer pruebas nuevas.

41.21 Tabla de buenas prácticas

Esta tabla resume prácticas útiles para mantener una suite unitaria sana.

Práctica Beneficio Ejemplo
Nombres por comportamiento. Fallas más fáciles de interpretar. test_edad_18_puede_registrarse
Datos simples. Resultados esperados más claros. 1000 para porcentajes.
Estado limpio por prueba. Independencia y determinismo. Fixture con alcance por función.
Separar niveles de prueba. Suite unitaria más rápida. tests/unit y tests/integration
Refactorizar pruebas. Menos ruido y mejor mantenimiento. Extraer preparación repetida.

41.22 Checklist de mantenimiento

Una revisión periódica de la suite puede usar preguntas como estas:

  • ¿Las pruebas unitarias siguen siendo rápidas?
  • ¿Las fallas indican claramente qué comportamiento se rompió?
  • ¿Hay pruebas intermitentes?
  • ¿Hay fixtures demasiado grandes o genéricas?
  • ¿Los casos límite importantes están cubiertos?
  • ¿Hay pruebas que solo aumentan cobertura sin aserciones útiles?
  • ¿La organización de archivos sigue siendo fácil de navegar?
  • ¿Las pruebas se ejecutan en el flujo habitual de desarrollo?

Estas preguntas ayudan a detectar problemas antes de que la suite se vuelva difícil de usar.

41.23 Qué debes recordar de este tema

  • Una suite útil debe ser rápida, clara, independiente y determinística.
  • Las pruebas pequeñas y bien nombradas facilitan el diagnóstico.
  • Los datos simples y los casos límite aportan claridad y valor.
  • Las fixtures y la parametrización deben mejorar la lectura, no ocultar intención.
  • Las pruebas unitarias deben separarse de pruebas más costosas.
  • Las fallas intermitentes deben investigarse, no ignorarse.
  • Las pruebas requieren mantenimiento igual que el código productivo.

41.24 Conclusión

Mantener una suite rápida y clara es una tarea continua. No alcanza con escribir pruebas al comienzo; hay que revisarlas, refactorizarlas y adaptarlas cuando el sistema cambia.

Una buena suite de pruebas unitarias funciona como una herramienta diaria de trabajo. Debe ejecutarse con frecuencia, fallar con mensajes útiles y ayudar a modificar código con mayor confianza.

En el próximo tema cerraremos el curso con un caso práctico integrador, aplicando muchas de las ideas estudiadas para probar una unidad completa.