22. Ejecución paralela de pruebas con pytest-xdist

22.1 Objetivo del tema

Cuando una suite crece, ejecutarla completa puede tardar cada vez más. Una forma de reducir el tiempo total es ejecutar pruebas en paralelo.

En este tema usaremos pytest-xdist para paralelizar la suite y veremos qué condiciones debe cumplir una prueba para ejecutarse correctamente junto a otras.

Objetivo práctico: ejecutar pruebas en paralelo sin romper el aislamiento ni ocultar problemas de diseño.

22.2 Qué es pytest-xdist

pytest-xdist es una extensión de pytest que permite distribuir pruebas en varios procesos. Cada proceso ejecuta una parte de la suite.

Esto puede reducir el tiempo total de ejecución, especialmente cuando hay muchas pruebas independientes.

22.3 Verificar instalación

Si ya seguiste el tema de instalación de herramientas, pytest-xdist debería estar instalado. Para instalarlo o confirmarlo:

python -m pip install pytest-xdist

Luego ejecuta:

python -m pytest --version

La salida debe mostrar que pytest está disponible. Si pytest-xdist está instalado, podrás usar la opción -n.

22.4 Ejecutar pruebas en paralelo

Para ejecutar la suite usando procesos automáticos:

python -m pytest -n auto

-n auto permite que pytest-xdist elija la cantidad de procesos según el equipo.

22.5 Elegir cantidad de procesos

También puedes indicar una cantidad específica:

python -m pytest -n 2

Este comando ejecuta pruebas usando dos procesos. A veces conviene usar un número fijo para que el comportamiento sea más predecible.

22.6 Cuándo conviene paralelizar

La ejecución paralela suele ayudar cuando:

  • La suite tiene muchas pruebas independientes.
  • Las pruebas tardan lo suficiente como para justificar varios procesos.
  • No hay datos compartidos que puedan pisarse entre pruebas.
  • Las pruebas crean sus propios archivos temporales.
  • No dependen del orden de ejecución.

22.7 Cuándo no conviene paralelizar todavía

Puede ser mejor no paralelizar si:

  • La suite es pequeña y ya ejecuta rápido.
  • Hay pruebas frágiles o dependientes de orden.
  • Las pruebas escriben en los mismos archivos.
  • Hay recursos compartidos sin aislamiento.
  • Primero necesitas diagnosticar fallas intermitentes.

22.8 Prueba apta para ejecución paralela

Una prueba que usa tmp_path suele ser apta para paralelo porque cada prueba recibe su propia carpeta temporal:

from app.archivos import escribir_texto


def test_escribir_texto_crea_archivo_con_contenido(tmp_path):
    ruta = tmp_path / "salida.txt"

    escribir_texto(ruta, "Contenido")

    assert ruta.read_text(encoding="utf-8") == "Contenido"

No comparte archivos con otras pruebas.

22.9 Prueba problemática en paralelo

Esta prueba puede causar problemas si varias pruebas escriben el mismo archivo:

from pathlib import Path


def test_escribe_archivo_compartido():
    ruta = Path("salida.txt")
    ruta.write_text("dato", encoding="utf-8")

    assert ruta.exists()

Si otras pruebas usan salida.txt, pueden interferir entre sí. Es mejor usar tmp_path.

22.10 Evitar estado global mutable

El estado global puede causar fallas difíciles de diagnosticar. Por ejemplo:

contador = 0


def incrementar():
    global contador
    contador += 1
    return contador

Si varias pruebas dependen del valor de contador, el resultado puede variar según el proceso y el orden. Es mejor evitar estado global o reiniciarlo explícitamente con fixtures.

22.11 Detectar pruebas dependientes de orden

Una señal de mala independencia es que una prueba pase sola, pero falle cuando se ejecuta con toda la suite o en paralelo.

Ejecuta una prueba específica y luego toda la suite:

python -m pytest tests/test_archivos.py
python -m pytest -n auto

Si el resultado cambia, revisa estado compartido, archivos compartidos o dependencias externas.

22.12 Ver distribución de pruebas

La salida con -n muestra que se crearon workers. Por ejemplo:

created: 2/2 workers

Eso indica que pytest-xdist está distribuyendo pruebas en procesos separados.

22.13 Usar -n auto con selección

Puedes combinar paralelo con selección por marcador:

python -m pytest -n auto -m "not lento"

También con una carpeta:

python -m pytest tests -n auto

22.14 Actualizar run_tests.py

Agrega un argumento para ejecución paralela:

parser.add_argument("--paralelo", action="store_true", help="Ejecuta pruebas en paralelo con pytest-xdist")

Luego, en construir_comando:

if args.paralelo:
    comando.extend(["-n", "auto"])

Así puedes ejecutar:

python run_tests.py --paralelo

22.15 Permitir cantidad de procesos

Una alternativa más flexible es aceptar un número:

parser.add_argument("--workers", help="Cantidad de procesos para pytest-xdist")

Y en construir_comando:

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

Uso:

python run_tests.py --workers auto
python run_tests.py --workers 2

22.16 Marcador para pruebas no paralelizables

Si una prueba no puede ejecutarse en paralelo por una razón concreta, declárala con un marcador:

markers =
    no_paralelo: pruebas que no deben ejecutarse en paralelo por usar recursos compartidos

Luego marca la prueba:

import pytest


@pytest.mark.no_paralelo
def test_recurso_compartido():
    assert True

22.17 Excluir pruebas no paralelizables

Para ejecutar en paralelo excluyendo esas pruebas:

python -m pytest -n auto -m "not no_paralelo"

Luego puedes ejecutar las no paralelizables aparte:

python -m pytest -m no_paralelo

Lo ideal es reducir al mínimo este grupo.

22.18 Reportes con ejecución paralela

También puedes generar reportes HTML con ejecución paralela:

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

Si el reporte falla o queda incompleto, prueba primero sin paralelizar para aislar si el problema está en las pruebas o en la configuración del reporte.

22.19 Buenas prácticas para paralelizar

  • Cada prueba debe preparar sus propios datos.
  • Usa tmp_path para archivos temporales.
  • No dependas del orden de ejecución.
  • Evita estado global mutable.
  • Controla variables de entorno con monkeypatch.
  • Ejecuta primero sin paralelo si aparece una falla extraña.

22.20 Problemas frecuentes

  • La opción -n no existe: instala pytest-xdist.
  • Falla solo en paralelo: busca archivos compartidos, estado global o dependencia de orden.
  • La ejecución paralela no mejora el tiempo: la suite puede ser demasiado pequeña o tener mucho costo de arranque.
  • Pruebas lentas saturan el equipo: usa menos workers, por ejemplo -n 2.
  • Reportes fallan en paralelo: prueba primero sin -n para aislar el problema.

22.21 Ejercicio práctico

Actualiza run_tests.py para aceptar --workers. Debe permitir estos comandos:

python run_tests.py --workers auto
python run_tests.py --workers 2
python run_tests.py --workers auto --tipo rapida

Luego ejecuta la suite en paralelo y revisa si aparece alguna falla que no estaba en la ejecución secuencial.

22.22 Solución propuesta

En leer_argumentos:

parser.add_argument("--workers", help="Cantidad de procesos para pytest-xdist")

En construir_comando:

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

Ejecuta:

python run_tests.py --workers auto

22.23 Lista de verificación

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

  • pytest-xdist está instalado.
  • Puedes ejecutar python -m pytest -n auto.
  • Las pruebas no dependen del orden de ejecución.
  • Las pruebas no escriben archivos compartidos.
  • Usas tmp_path para archivos temporales.
  • El script puede aceptar --workers si lo implementaste.
  • Investigaste cualquier falla que aparezca solo en paralelo.

22.24 Conclusión

En este tema ejecutamos pruebas en paralelo con pytest-xdist. La paralelización puede reducir tiempos, pero exige pruebas bien aisladas y sin estado compartido.

En el próximo tema veremos reportes en consola: salida corta, detallada, trazas y tiempos de ejecución.