11. Separar pruebas rápidas, lentas, críticas y de regresión

11.1 Objetivo del tema

En el tema anterior aprendimos a usar marcadores de pytest. Ahora vamos a aplicar esos marcadores con un criterio práctico: separar pruebas rápidas, lentas, críticas y de regresión.

Esta separación permite elegir qué ejecutar durante el desarrollo, antes de cerrar un cambio o cuando necesitamos una verificación más completa.

Objetivo práctico: clasificar la suite por velocidad y riesgo para ejecutar el grupo adecuado en cada momento.

11.2 Por qué separar pruebas

No todas las pruebas tienen el mismo costo ni el mismo valor. Algunas se ejecutan en milisegundos y conviene correrlas todo el tiempo. Otras tardan más, dependen de más datos o verifican flujos más amplios.

Separar pruebas ayuda a:

  • Obtener feedback rápido mientras programamos.
  • Ejecutar pruebas críticas antes de entregar un cambio.
  • Evitar que pruebas lentas bloqueen el trabajo cotidiano.
  • Mantener una suite de regresión útil y clara.
  • Diagnosticar fallas por tipo de prueba.

11.3 Definir pruebas rápidas

Una prueba rápida se ejecuta en poco tiempo, no depende de servicios externos y prepara pocos datos. Suele validar una función, una regla o un comportamiento pequeño.

Ejemplo:

def test_validar_cupon_con_codigo_correcto_devuelve_true():
    assert validar_cupon("DESC10") is True

En muchos proyectos no hace falta marcar las pruebas rápidas. Podemos considerar rápidas a todas las que no estén marcadas como lento.

11.4 Definir pruebas lentas

Una prueba lenta tarda más que el promedio de la suite. Puede ser lenta porque procesa muchos datos, usa archivos grandes, espera algún tiempo o ejecuta un flujo más completo.

Ejemplo:

import time
import pytest


@pytest.mark.lento
def test_generar_reporte_grande_de_ejemplo():
    time.sleep(1)

    assert True

La marca lento permite excluirla cuando necesitamos una ejecución rápida.

11.5 Definir pruebas críticas

Una prueba crítica valida un comportamiento que no debería romperse nunca. Si falla, el sistema tiene un problema importante.

Ejemplo:

import pytest


@pytest.mark.critica
def test_calcular_total_con_varios_productos_devuelve_suma_total():
    productos = [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

    assert calcular_total(productos) == 350

No todas las pruebas deben ser críticas. Si todo es crítico, la etiqueta deja de aportar información.

11.6 Definir pruebas de regresión

Una prueba de regresión protege un comportamiento que ya funcionaba y que queremos evitar que vuelva a romperse. Muchas veces nace después de corregir un error.

Ejemplo:

import pytest


@pytest.mark.regresion
def test_normalizar_texto_con_espacios_multiples_devuelve_un_solo_espacio():
    assert normalizar_texto("curso   de    pruebas") == "curso de pruebas"

Las pruebas de regresión son especialmente útiles para errores que ya ocurrieron y podrían repetirse.

11.7 Declarar marcadores necesarios

En pytest.ini, asegúrate de tener estos marcadores:

markers =
    lento: pruebas que tardan más tiempo en ejecutarse
    regresion: pruebas importantes para detectar regresiones
    critica: pruebas indispensables para validar el comportamiento principal
    carrito: pruebas relacionadas con el carrito de compras
    texto: pruebas relacionadas con normalización y manejo de textos

También es recomendable mantener --strict-markers en addopts para evitar errores de escritura.

11.8 Ejecutar la suite rápida

Si consideramos rápidas a todas las pruebas que no están marcadas como lentas, ejecutamos:

python -m pytest -m "not lento"

Este comando es muy útil durante el desarrollo diario.

11.9 Ejecutar pruebas lentas

Para ejecutar solo las pruebas lentas:

python -m pytest -m lento

Esto permite revisar el grupo costoso en momentos específicos, sin mezclarlo con la ejecución rápida.

11.10 Ejecutar pruebas críticas

Para ejecutar solo pruebas críticas:

python -m pytest -m critica

Este grupo debería ejecutarse con frecuencia, porque protege comportamientos fundamentales.

11.11 Ejecutar pruebas de regresión

Para ejecutar pruebas de regresión:

python -m pytest -m regresion

También puedes excluir las lentas si hay regresiones más costosas:

python -m pytest -m "regresion and not lento"

11.12 Combinar criticidad y velocidad

Podemos ejecutar pruebas críticas que no sean lentas:

python -m pytest -m "critica and not lento"

También podemos ejecutar todo lo crítico y todo lo de regresión:

python -m pytest -m "critica or regresion"

Estas combinaciones permiten adaptar la ejecución al momento del trabajo.

11.13 Marcar pruebas con más de una categoría

Una prueba puede ser crítica y de regresión al mismo tiempo:

import pytest


@pytest.mark.critica
@pytest.mark.regresion
def test_calcular_total_con_varios_productos_devuelve_suma_total():
    productos = [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

    assert calcular_total(productos) == 350

Esto es correcto si ambas categorías aportan información útil.

11.14 Crear una prueba lenta controlada

Para practicar, crea tests/test_procesos_lentos.py:

import time
import pytest


@pytest.mark.lento
def test_proceso_lento_de_ejemplo_finaliza_correctamente():
    time.sleep(1)

    assert True

Esta prueba es solo demostrativa. En un proyecto real, una prueba lenta debería tener una razón concreta.

11.15 Actualizar run_tests.py para suites predefinidas

Podemos agregar un argumento --tipo al script:

parser.add_argument(
    "--tipo",
    choices=["rapida", "lenta", "critica", "regresion"],
    help="Ejecuta una suite predefinida",
)

Luego, en construir_comando:

if args.tipo == "rapida":
    comando.extend(["-m", "not lento"])
elif args.tipo == "lenta":
    comando.extend(["-m", "lento"])
elif args.tipo == "critica":
    comando.extend(["-m", "critica"])
elif args.tipo == "regresion":
    comando.extend(["-m", "regresion"])

11.16 Ejecutar suites predefinidas desde el script

Con el argumento --tipo, puedes ejecutar:

python run_tests.py --tipo rapida
python run_tests.py --tipo lenta
python run_tests.py --tipo critica
python run_tests.py --tipo regresion

Esto hace que la ejecución sea más expresiva para quien no recuerda la sintaxis exacta de -m.

11.17 Criterios para clasificar una prueba

Antes de marcar una prueba, revisa estos criterios:

  • Rápida: no requiere marca si el proyecto considera rápidas a todas las no lentas.
  • Lenta: tarda más que el promedio o requiere recursos costosos.
  • Crítica: si falla, el sistema queda en un estado inaceptable.
  • Regresión: protege un error corregido o una regla que no debe volver a romperse.

11.18 Evitar clasificaciones incorrectas

Una mala clasificación puede causar problemas. Por ejemplo, si marcas demasiadas pruebas como lentas, la suite rápida pierde cobertura. Si marcas demasiadas como críticas, ya no sabes cuáles son verdaderamente indispensables.

La clasificación debe revisarse cuando la suite crece. No es una decisión definitiva para siempre.

11.19 Documentar la estrategia

Agrega al README.md una sección como esta:

## Tipos de ejecución

Suite rápida:

python -m pytest -m "not lento"

Pruebas lentas:

python -m pytest -m lento

Pruebas críticas:

python -m pytest -m critica

Pruebas de regresión:

python -m pytest -m regresion

Documentar la estrategia evita que cada persona use criterios diferentes.

11.20 Problemas frecuentes

  • La suite rápida ejecuta pruebas lentas: revisa si esas pruebas tienen el marcador lento.
  • La suite crítica tiene demasiadas pruebas: revisa si todas son realmente indispensables.
  • No se ejecuta ninguna regresión: verifica que existan pruebas marcadas como regresion.
  • Una expresión con -m falla: usa comillas cuando incluye espacios o conectores.
  • El script no reconoce --tipo: revisa que hayas agregado el argumento en leer_argumentos.

11.21 Ejercicio práctico

Clasifica al menos cinco pruebas del proyecto:

  • Dos pruebas críticas.
  • Dos pruebas de regresión.
  • Una prueba lenta de ejemplo.

Luego ejecuta:

python -m pytest -m "not lento"
python -m pytest -m critica
python -m pytest -m regresion
python -m pytest -m lento

11.22 Solución propuesta

Ejemplo de prueba crítica y de regresión:

import pytest

from app.carrito import calcular_total


@pytest.mark.critica
@pytest.mark.regresion
def test_calcular_total_con_varios_productos_devuelve_suma_total():
    productos = [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

    assert calcular_total(productos) == 350

Ejemplo de prueba lenta:

import time
import pytest


@pytest.mark.lento
def test_proceso_lento_de_ejemplo_finaliza_correctamente():
    time.sleep(1)

    assert True

11.23 Lista de verificación

Antes de continuar con el próximo tema, verifica lo siguiente:

  • Las pruebas lentas están marcadas con @pytest.mark.lento.
  • Las pruebas críticas están marcadas con @pytest.mark.critica.
  • Las pruebas de regresión están marcadas con @pytest.mark.regresion.
  • La suite rápida se ejecuta con python -m pytest -m "not lento".
  • El script puede ejecutar suites predefinidas si agregaste --tipo.
  • La estrategia está documentada en el README.
  • Ejecutas la suite completa antes de cerrar cambios importantes.

11.24 Conclusión

En este tema separamos la suite por velocidad y riesgo. Esta clasificación permite elegir la ejecución adecuada para cada momento: rápida durante el desarrollo, crítica antes de entregar, regresión para prevenir errores conocidos y lenta cuando se necesita una revisión más completa.

En el próximo tema trabajaremos con fixtures reutilizables para preparar datos, objetos y estados iniciales de forma ordenada.