24. Generación de reportes HTML y JUnit XML

24.1 Objetivo del tema

La consola es útil para el trabajo diario, pero muchas veces necesitamos conservar evidencia de una ejecución o compartir resultados con otra herramienta.

En este tema generaremos reportes HTML para lectura humana y archivos JUnit XML para integración con herramientas externas.

Objetivo práctico: generar reportes de pruebas en HTML y XML desde comandos y desde run_tests.py.

24.2 Tipos de reportes

Usaremos dos formatos:

  • HTML: cómodo para abrir en un navegador y revisar resultados.
  • JUnit XML: formato usado por muchas herramientas para leer resultados de pruebas.

Ambos pueden generarse desde pytest.

24.3 Verificar pytest-html

Para generar HTML necesitamos pytest-html. Instálalo si hace falta:

python -m pip install pytest-html

Luego puedes ejecutar:

python -m pytest --version

Si la instalación fue correcta, pytest aceptará la opción --html.

24.4 Crear carpeta reports

Si todavía no existe, crea la carpeta de reportes:

mkdir reports

En los scripts de Python también podemos crearla automáticamente con Path("reports").mkdir(exist_ok=True).

24.5 Generar un reporte HTML

Ejecuta:

python -m pytest --html=reports/reporte.html --self-contained-html

La opción --self-contained-html genera un HTML autocontenido, más fácil de compartir porque incluye estilos dentro del mismo archivo.

24.6 Abrir el reporte HTML

Después de ejecutar el comando, abre reports/reporte.html en el navegador.

El reporte muestra información como:

  • Pruebas ejecutadas.
  • Pruebas exitosas.
  • Pruebas fallidas.
  • Duración.
  • Detalles de errores.

24.7 Generar JUnit XML

pytest puede generar JUnit XML sin instalar extensiones adicionales:

python -m pytest --junitxml=reports/junit.xml

El archivo XML no está pensado principalmente para lectura humana. Sirve para que otras herramientas puedan procesar resultados.

24.8 Generar HTML y XML juntos

Podemos generar ambos reportes en una sola ejecución:

python -m pytest --html=reports/reporte.html --self-contained-html --junitxml=reports/junit.xml

Esto permite tener evidencia visual y un archivo estructurado para herramientas.

24.9 Reportes con selección de pruebas

Los reportes también pueden generarse para una parte de la suite:

python -m pytest tests/test_carrito.py --html=reports/carrito.html --self-contained-html

Esto es útil cuando quieres evidencia de una funcionalidad concreta.

24.10 Reportes con marcadores

También puedes generar reportes para pruebas marcadas:

python -m pytest -m critica --html=reports/criticas.html --self-contained-html

El nombre del archivo debería reflejar qué grupo de pruebas se ejecutó.

24.11 Nombrar reportes con fecha y hora

Para no sobrescribir reportes anteriores, podemos usar una marca de tiempo en el nombre. En Python:

from datetime import datetime


def crear_nombre_reporte():
    marca = datetime.now().strftime("%Y%m%d_%H%M%S")
    return f"reporte_{marca}.html"

Esto genera nombres como reporte_20260510_153000.html.

24.12 Actualizar run_tests.py para HTML

En run_tests.py, asegúrate de tener una carpeta de reportes:

from pathlib import Path


REPORTS_DIR = Path("reports")

Y al construir el comando:

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

24.13 Agregar opción para JUnit XML

Agrega un argumento:

parser.add_argument("--junit", action="store_true", help="Genera reporte JUnit XML")

Y en construir_comando:

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

Uso:

python run_tests.py --junit

24.14 Generar ambos reportes desde el script

Con las opciones anteriores puedes ejecutar:

python run_tests.py --reporte --junit

Esto debería generar:

reports/reporte.html
reports/junit.xml

24.15 Reportes y códigos de salida

Generar reportes no significa que la ejecución haya sido exitosa. El script debe seguir devolviendo el código de salida de pytest.

resultado = subprocess.run(comando, check=False)
return resultado.returncode

Si una prueba falla, el reporte se genera, pero el código de salida debe indicar fallo.

24.16 Reportes y .gitignore

Normalmente los reportes generados no se suben al repositorio. Agrega en .gitignore:

reports/

La carpeta puede recrearse en cada ejecución. Lo importante es conservar los comandos que generan los reportes, no los archivos generados.

24.17 Limpiar reportes anteriores

Podemos agregar una opción para limpiar reportes antes de ejecutar:

import shutil


def limpiar_reportes():
    if REPORTS_DIR.exists():
        shutil.rmtree(REPORTS_DIR)
    REPORTS_DIR.mkdir()

Úsalo con cuidado. No limpies reportes si necesitas conservar evidencia histórica.

24.18 Agregar --limpiar-reportes

Argumento:

parser.add_argument("--limpiar-reportes", action="store_true", help="Elimina reportes previos antes de ejecutar")

Uso en main antes de ejecutar pytest:

if args.limpiar_reportes:
    limpiar_reportes()

Comando:

python run_tests.py --limpiar-reportes --reporte --junit

24.19 Reportes con ejecución paralela

Si usas pytest-xdist, también puedes generar reportes:

python -m pytest -n auto --html=reports/reporte.html --self-contained-html --junitxml=reports/junit.xml

Si aparece un problema extraño, ejecuta primero sin -n auto para distinguir si el problema es de paralelización o del reporte.

24.20 Buenas prácticas

  • Guarda reportes en una carpeta específica como reports.
  • Usa nombres claros para reportes por suite o marcador.
  • No subas reportes generados al repositorio salvo que haya una razón concreta.
  • Conserva el código de salida real de pytest.
  • Genera HTML para lectura humana y XML para herramientas.

24.21 Problemas frecuentes

  • pytest no reconoce --html: instala pytest-html.
  • No aparece el reporte: verifica que exista la carpeta reports o que el script la cree.
  • El XML parece difícil de leer: es normal; está pensado para herramientas.
  • El reporte se sobrescribe: usa nombres con fecha o por tipo de suite.
  • El script devuelve error aunque hay reporte: revisa si alguna prueba falló.

24.22 Ejercicio práctico

Actualiza run_tests.py para soportar:

  • --reporte para HTML.
  • --junit para XML.
  • --limpiar-reportes para borrar reportes previos.

Luego ejecuta:

python run_tests.py --limpiar-reportes --reporte --junit

24.23 Solución propuesta

Fragmentos principales:

import shutil
from pathlib import Path


REPORTS_DIR = Path("reports")


def limpiar_reportes():
    if REPORTS_DIR.exists():
        shutil.rmtree(REPORTS_DIR)
    REPORTS_DIR.mkdir()


parser.add_argument("--junit", action="store_true", help="Genera reporte JUnit XML")
parser.add_argument("--limpiar-reportes", action="store_true", help="Elimina reportes previos antes de ejecutar")

En la construcción del comando:

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")

24.24 Lista de verificación

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

  • pytest-html está instalado.
  • Puedes generar reports/reporte.html.
  • Puedes generar reports/junit.xml.
  • El script conserva el código de salida de pytest.
  • reports/ está en .gitignore si corresponde.
  • Los reportes tienen nombres claros.
  • Puedes generar ambos formatos desde run_tests.py.

24.25 Conclusión

En este tema generamos reportes HTML y JUnit XML. El HTML ayuda a revisar resultados visualmente y el XML permite que otras herramientas procesen la ejecución.

En el próximo tema automatizaremos tareas repetitivas con scripts de Python para seguir simplificando el trabajo diario con la suite.