26. Crear comandos simples para ejecutar suites por objetivo

26.1 Objetivo del tema

En temas anteriores agregamos varias opciones a run_tests.py. Cuando un script tiene muchas opciones, puede volverse difícil recordar qué combinación usar para cada situación.

En este tema crearemos comandos simples para ejecutar suites por objetivo: rápida, crítica, regresión, lenta, con reporte, en paralelo o de diagnóstico.

Objetivo práctico: transformar combinaciones de opciones en comandos claros y fáciles de recordar.

26.2 Qué es ejecutar por objetivo

Ejecutar por objetivo significa elegir una intención antes que una lista de opciones técnicas. Por ejemplo:

  • rapida: ejecutar todo excepto pruebas lentas.
  • critica: ejecutar pruebas indispensables.
  • regresion: ejecutar pruebas de regresión.
  • completa: ejecutar toda la suite.
  • diagnostico: ejecutar con más información.

El usuario del script no necesita recordar todos los flags internos de pytest.

26.3 Comandos deseados

Queremos poder ejecutar comandos como estos:

python run_tests.py rapida
python run_tests.py critica
python run_tests.py regresion
python run_tests.py completa
python run_tests.py diagnostico

Cada objetivo construirá el comando de pytest correspondiente.

26.4 Estructura inicial con argparse

Actualiza run_tests.py para aceptar un objetivo posicional:

import argparse
import subprocess
import sys
from pathlib import Path


REPORTS_DIR = Path("reports")


def leer_argumentos():
    parser = argparse.ArgumentParser(description="Ejecuta suites de pruebas por objetivo")
    parser.add_argument(
        "objetivo",
        nargs="?",
        default="completa",
        choices=["completa", "rapida", "critica", "regresion", "lenta", "diagnostico"],
        help="Objetivo de ejecución",
    )
    return parser.parse_args()

Si no se indica objetivo, usaremos completa.

26.5 Construir comando por objetivo

Crea una función para traducir objetivos a opciones de pytest:

def construir_comando_por_objetivo(objetivo):
    comando = [sys.executable, "-m", "pytest"]

    if objetivo == "rapida":
        comando.extend(["-m", "not lento"])
    elif objetivo == "critica":
        comando.extend(["-m", "critica"])
    elif objetivo == "regresion":
        comando.extend(["-m", "regresion"])
    elif objetivo == "lenta":
        comando.extend(["-m", "lento"])
    elif objetivo == "diagnostico":
        comando.extend(["-v", "--tb=short", "--durations=5", "-ra"])

    return comando

El objetivo completa no agrega filtros.

26.6 Ejecutar el comando

Agrega la función principal:

def main():
    args = leer_argumentos()
    comando = construir_comando_por_objetivo(args.objetivo)
    print("Ejecutando:", " ".join(comando))
    resultado = subprocess.run(comando, check=False)
    return resultado.returncode


if __name__ == "__main__":
    raise SystemExit(main())

Ahora puedes ejecutar:

python run_tests.py rapida

26.7 Mantener opciones adicionales

Podemos conservar opciones como --reporte, --junit y --workers:

parser.add_argument("--reporte", action="store_true", help="Genera reporte HTML")
parser.add_argument("--junit", action="store_true", help="Genera reporte JUnit XML")
parser.add_argument("--workers", help="Cantidad de procesos para pytest-xdist")

Así podemos combinar objetivo y opciones:

python run_tests.py rapida --reporte
python run_tests.py regresion --workers auto

26.8 Agregar reportes al comando

Extiende la construcción del comando:

def agregar_opciones_generales(comando, args):
    if args.workers:
        comando.extend(["-n", args.workers])

    if args.reporte:
        REPORTS_DIR.mkdir(exist_ok=True)
        comando.extend([
            "--html=reports/reporte.html",
            "--self-contained-html",
        ])

    if args.junit:
        REPORTS_DIR.mkdir(exist_ok=True)
        comando.append("--junitxml=reports/junit.xml")

    return comando

26.9 main con opciones generales

La función main puede quedar así:

def main():
    args = leer_argumentos()
    comando = construir_comando_por_objetivo(args.objetivo)
    comando = agregar_opciones_generales(comando, args)
    print("Ejecutando:", " ".join(comando))
    resultado = subprocess.run(comando, check=False)
    return resultado.returncode

El script conserva el código de salida real de pytest.

26.10 Comandos por objetivo

Ejecuta cada objetivo:

python run_tests.py completa
python run_tests.py rapida
python run_tests.py critica
python run_tests.py regresion
python run_tests.py lenta
python run_tests.py diagnostico

Si alguna categoría no tiene pruebas marcadas, pytest puede indicar que no encontró pruebas para esa selección.

26.11 Agregar ayuda clara

argparse genera ayuda automáticamente:

python run_tests.py --help

Es importante que los textos de ayuda expliquen el objetivo de cada opción.

26.12 Validar objetivos con choices

El parámetro choices evita objetivos inválidos:

choices=["completa", "rapida", "critica", "regresion", "lenta", "diagnostico"]

Si alguien escribe un objetivo incorrecto, el script muestra un error claro antes de ejecutar pytest.

26.13 Agregar objetivo reporte

Podemos crear un objetivo que genere HTML y XML automáticamente:

choices=[
    "completa",
    "rapida",
    "critica",
    "regresion",
    "lenta",
    "diagnostico",
    "reporte",
]

Y en main:

if args.objetivo == "reporte":
    args.reporte = True
    args.junit = True

Uso:

python run_tests.py reporte

26.14 Evitar demasiados objetivos

No conviene crear un objetivo para cada combinación posible. Los objetivos deben representar flujos reales de trabajo.

Buenos objetivos iniciales:

  • completa
  • rapida
  • critica
  • regresion
  • diagnostico
  • reporte

26.15 Integrar con scripts auxiliares

Los scripts del tema anterior pueden usar los nuevos objetivos:

pasos = [
    [sys.executable, "scripts/verificar_estructura.py"],
    [sys.executable, "run_tests.py", "rapida"],
]

Esto hace que la rutina sea más expresiva.

26.16 Documentar los objetivos

Agrega al README.md:

## Objetivos de ejecución

Suite completa:

python run_tests.py completa

Suite rápida:

python run_tests.py rapida

Pruebas críticas:

python run_tests.py critica

Pruebas de regresión:

python run_tests.py regresion

Diagnóstico:

python run_tests.py diagnostico

Reportes HTML y XML:

python run_tests.py reporte

26.17 Comandos y consistencia

Un comando simple debe significar siempre lo mismo. Si rapida significa not lento, mantén ese criterio en la documentación, en el script y en la comunicación del equipo.

La automatización pierde valor si cada persona interpreta los objetivos de forma distinta.

26.18 Mantener compatibilidad con rutas

Si quieres seguir permitiendo rutas específicas, agrega un argumento opcional:

parser.add_argument("--ruta", help="Archivo o carpeta de pruebas")

Y luego:

if args.ruta:
    comando.append(args.ruta)

Uso:

python run_tests.py rapida --ruta tests/test_carrito.py

26.19 Problemas frecuentes

  • Hay demasiados objetivos: deja solo los que se usan realmente.
  • Un objetivo no ejecuta pruebas: revisa los marcadores asociados.
  • La ayuda no se entiende: mejora los textos de help.
  • El objetivo cambia de significado: actualiza documentación y equipo al mismo tiempo.
  • Se duplican opciones en varios scripts: concentra la ejecución en run_tests.py.

26.20 Ejercicio práctico

Actualiza run_tests.py para soportar estos objetivos:

  • completa
  • rapida
  • critica
  • regresion
  • diagnostico
  • reporte

Luego prueba:

python run_tests.py --help
python run_tests.py rapida
python run_tests.py reporte

26.21 Solución propuesta

Fragmento principal:

def construir_comando_por_objetivo(objetivo):
    comando = [sys.executable, "-m", "pytest"]

    if objetivo == "rapida":
        comando.extend(["-m", "not lento"])
    elif objetivo == "critica":
        comando.extend(["-m", "critica"])
    elif objetivo == "regresion":
        comando.extend(["-m", "regresion"])
    elif objetivo == "diagnostico":
        comando.extend(["-v", "--tb=short", "--durations=5", "-ra"])

    return comando

Activar reportes desde el objetivo:

if args.objetivo == "reporte":
    args.reporte = True
    args.junit = True

26.22 Lista de verificación

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

  • El script acepta un objetivo posicional.
  • Los objetivos están validados con choices.
  • Cada objetivo corresponde a una intención clara.
  • Los objetivos usan python -m pytest internamente.
  • --help muestra ayuda comprensible.
  • El README documenta los objetivos principales.
  • El script conserva el código de salida de pytest.

26.23 Conclusión

En este tema creamos comandos simples para ejecutar suites por objetivo. Esto hace que la automatización sea más cómoda y reduce la necesidad de recordar combinaciones largas de opciones.

En el próximo tema veremos cómo limpiar automáticamente datos, archivos y recursos después de cada prueba.