Hasta ahora ejecutamos las pruebas escribiendo comandos de pytest desde la terminal. En este tema construiremos un script de Python para centralizar esa ejecución.
El script permitirá ejecutar la suite con un comando único, agregar opciones, crear la carpeta de reportes y devolver un código de salida correcto cuando las pruebas pasan o fallan.
Un script de ejecución evita que cada persona recuerde comandos distintos. En una suite automatizada, esto ayuda a mantener consistencia.
Un buen script permite:
El comando base del curso será:
python -m pytest
Lo importante es no depender del comando directo pytest. Al usar python -m pytest, ejecutamos pytest con el intérprete de Python activo en el proyecto.
Crea o reemplaza el archivo run_tests.py en la raíz del proyecto:
import subprocess
import sys
def main():
comando = [sys.executable, "-m", "pytest"]
resultado = subprocess.run(comando, check=False)
return resultado.returncode
if __name__ == "__main__":
raise SystemExit(main())
Ejecuta el script:
python run_tests.py
sys.executable representa el Python que está ejecutando el script. Así evitamos llamar a otro Python instalado en el sistema.
subprocess.run permite ejecutar un comando externo desde Python. En este caso, ejecutamos pytest como si lo escribiéramos en la terminal.
La lista:
[sys.executable, "-m", "pytest"]
equivale a ejecutar:
python -m pytest
La opción check=False evita que Python lance una excepción si las pruebas fallan. En su lugar, podemos leer el código de salida.
Cuando pytest termina, devuelve un número. Ese número permite saber si la ejecución fue exitosa o no.
0: todas las pruebas pasaron.1: alguna prueba falló.2: la ejecución fue interrumpida.4: hubo un error de uso del comando.5: no se encontraron pruebas.El script devuelve ese mismo código con raise SystemExit(main()). Esto será importante cuando otro proceso necesite saber si la suite pasó o falló.
Modifica el comando para que use la opción -v:
comando = [sys.executable, "-m", "pytest", "-v"]
Ahora la ejecución mostrará el nombre de cada prueba. El script completo queda así:
import subprocess
import sys
def main():
comando = [sys.executable, "-m", "pytest", "-v"]
resultado = subprocess.run(comando, check=False)
return resultado.returncode
if __name__ == "__main__":
raise SystemExit(main())
Si vamos a generar reportes, conviene que el script cree la carpeta antes de ejecutar la suite.
from pathlib import Path
REPORTS_DIR = Path("reports")
REPORTS_DIR.mkdir(exist_ok=True)
Path("reports").mkdir(exist_ok=True) crea la carpeta si no existe y no falla si ya fue creada.
Agrega las opciones de pytest-html al comando:
comando = [
sys.executable,
"-m",
"pytest",
"-v",
"--html=reports/reporte.html",
"--self-contained-html",
]
Así, cada ejecución creará o reemplazará el archivo reports/reporte.html.
El archivo run_tests.py puede quedar así:
import subprocess
import sys
from pathlib import Path
REPORTS_DIR = Path("reports")
def main():
REPORTS_DIR.mkdir(exist_ok=True)
comando = [
sys.executable,
"-m",
"pytest",
"-v",
"--html=reports/reporte.html",
"--self-contained-html",
]
resultado = subprocess.run(comando, check=False)
return resultado.returncode
if __name__ == "__main__":
raise SystemExit(main())
Ejecuta:
python run_tests.py
Ahora haremos que el script acepte opciones desde la terminal. Usaremos argparse, un módulo incluido en Python.
Primero importamos:
import argparse
Luego creamos una función para leer argumentos:
def leer_argumentos():
parser = argparse.ArgumentParser()
parser.add_argument("--rapido", action="store_true")
parser.add_argument("--reporte", action="store_true")
return parser.parse_args()
Con argumentos, podemos generar reporte solo cuando lo pedimos:
if args.reporte:
REPORTS_DIR.mkdir(exist_ok=True)
comando.extend([
"--html=reports/reporte.html",
"--self-contained-html",
])
Así el comando simple:
python run_tests.py
ejecuta la suite sin reporte, y este comando:
python run_tests.py --reporte
ejecuta la suite y genera el archivo HTML.
En temas posteriores clasificaremos pruebas con marcadores. Por ahora dejaremos preparado un argumento --rapido para ejecutar pruebas que no estén marcadas como lentas.
if args.rapido:
comando.extend(["-m", "not lento"])
Más adelante agregaremos el marcador lento en la configuración del proyecto. La idea es que el script ya tenga una forma clara de ejecutar una suite reducida.
Reemplaza run_tests.py por esta versión:
import argparse
import subprocess
import sys
from pathlib import Path
REPORTS_DIR = Path("reports")
def leer_argumentos():
parser = argparse.ArgumentParser(description="Ejecuta la suite automatizada")
parser.add_argument("--rapido", action="store_true", help="Excluye pruebas marcadas como lentas")
parser.add_argument("--reporte", action="store_true", help="Genera un reporte HTML")
return parser.parse_args()
def construir_comando(args):
comando = [sys.executable, "-m", "pytest", "-v"]
if args.rapido:
comando.extend(["-m", "not lento"])
if args.reporte:
REPORTS_DIR.mkdir(exist_ok=True)
comando.extend([
"--html=reports/reporte.html",
"--self-contained-html",
])
return comando
def main():
args = leer_argumentos()
comando = construir_comando(args)
resultado = subprocess.run(comando, check=False)
return resultado.returncode
if __name__ == "__main__":
raise SystemExit(main())
Ejecuta la suite completa:
python run_tests.py
Ejecuta la suite con reporte:
python run_tests.py --reporte
Ejecuta la suite rápida:
python run_tests.py --rapido
Ejecuta la suite rápida y genera reporte:
python run_tests.py --rapido --reporte
Para que el script sea más transparente, podemos imprimir el comando final:
print("Ejecutando:", " ".join(comando))
Agrega esa línea antes de subprocess.run:
def main():
args = leer_argumentos()
comando = construir_comando(args)
print("Ejecutando:", " ".join(comando))
resultado = subprocess.run(comando, check=False)
return resultado.returncode
Esto ayuda a diagnosticar qué opciones está usando realmente la ejecución.
Un script de ejecución debe ser útil, pero no debe convertirse en una aplicación compleja. Conviene mantenerlo simple y enfocado.
Evita agregarle responsabilidades como:
pytest.ini.Agrega al README.md una sección para documentar el uso del script:
## Ejecutar la suite automatizada
python run_tests.py
## Ejecutar y generar reporte HTML
python run_tests.py --reporte
## Ejecutar suite rápida
python run_tests.py --rapido
La automatización también necesita documentación breve. Un comando útil pierde valor si nadie sabe cómo usarlo.
pytest-html.lento en pytest.ini cuando empieces a usarlo.Agrega al script un nuevo argumento llamado --detener. Cuando se use, debe agregar la opción -x al comando de pytest para detener la ejecución en la primera falla.
Luego prueba estos comandos:
python run_tests.py --detener
python run_tests.py --detener --reporte
Agrega el argumento en leer_argumentos:
parser.add_argument("--detener", action="store_true", help="Detiene la ejecución en la primera falla")
Luego agrega esta condición en construir_comando:
if args.detener:
comando.append("-x")
La función completa puede quedar así:
def construir_comando(args):
comando = [sys.executable, "-m", "pytest", "-v"]
if args.rapido:
comando.extend(["-m", "not lento"])
if args.detener:
comando.append("-x")
if args.reporte:
REPORTS_DIR.mkdir(exist_ok=True)
comando.extend([
"--html=reports/reporte.html",
"--self-contained-html",
])
return comando
Antes de continuar con el próximo tema, verifica lo siguiente:
run_tests.py en la raíz del proyecto.sys.executable, -m y pytest.--reporte genera un archivo HTML.--rapido agrega una selección de pruebas.--detener detiene la suite en la primera falla.En este tema creamos un primer script de ejecución automática para la suite de pruebas. El script centraliza el comando, usa el Python correcto, permite opciones simples y conserva el código de salida de pytest.
Este tipo de automatización reduce errores de ejecución y prepara el proyecto para flujos más avanzados. En el próximo tema trabajaremos con la configuración centralizada usando pyproject.toml y pytest.ini.