32. Ejecutar pruebas en distintas versiones de Python con tox

32.1 Objetivo del tema

Un proyecto puede funcionar en una versión de Python y fallar en otra. Si una librería declara compatibilidad con varias versiones, conviene ejecutar la misma suite en cada una.

tox automatiza ese trabajo: crea entornos aislados, instala dependencias y ejecuta comandos de prueba.

Idea clave: tox ayuda a comprobar que el proyecto se prueba igual en distintos entornos.

32.2 Crear una carpeta de práctica

Crea un proyecto nuevo:

mkdir tox-demo
cd tox-demo

Instala las herramientas necesarias en tu entorno principal:

python -m pip install pytest tox

32.3 Crear una estructura simple

Crea estas carpetas y archivos:

mkdir src
mkdir src\calculadora
mkdir tests
New-Item src\calculadora\__init__.py -ItemType File
New-Item src\calculadora\operaciones.py -ItemType File
New-Item tests\test_operaciones.py -ItemType File

32.4 Crear el módulo a probar

En src\calculadora\operaciones.py escribe:

def sumar(a, b):
    return a + b


def dividir(a, b):
    if b == 0:
        raise ValueError("No se puede dividir por cero")
    return a / b


def promedio(numeros):
    if not numeros:
        raise ValueError("La lista no puede estar vacía")
    return sum(numeros) / len(numeros)

32.5 Crear pruebas

En tests\test_operaciones.py escribe:

import pytest

from calculadora.operaciones import dividir, promedio, sumar


def test_sumar():
    assert sumar(2, 3) == 5


def test_dividir():
    assert dividir(10, 2) == 5


def test_dividir_por_cero():
    with pytest.raises(ValueError):
        dividir(10, 0)


def test_promedio():
    assert promedio([10, 20, 30]) == 20


def test_promedio_lista_vacia():
    with pytest.raises(ValueError):
        promedio([])

32.6 Crear pytest.ini

Para que pytest encuentre el paquete dentro de src, crea pytest.ini:

[pytest]
pythonpath = src
testpaths = tests
addopts = -ra

Esto permite ejecutar pruebas localmente con:

python -m pytest

32.7 Crear tox.ini

Crea un archivo tox.ini en la raíz:

[tox]
envlist = py310, py311, py312
skip_missing_interpreters = true

[testenv]
deps =
    pytest
commands =
    python -m pytest

envlist define los entornos que tox intentará ejecutar.

32.8 Qué significa cada sección

  • [tox]: configuración general de tox.
  • envlist: lista de versiones o entornos a ejecutar.
  • skip_missing_interpreters: omite versiones de Python que no estén instaladas.
  • [testenv]: configuración común para cada entorno.
  • deps: dependencias que tox instala dentro del entorno.
  • commands: comandos que tox ejecuta.

32.9 Ejecutar tox

Ejecuta:

python -m tox

tox creará una carpeta .tox con entornos aislados y ejecutará las pruebas en cada versión disponible.

py310: skipped because could not find python interpreter
py311: OK
py312: OK
congratulations :)

La salida exacta depende de las versiones instaladas en tu equipo.

32.10 Ejecutar un solo entorno

Para ejecutar solo un entorno:

python -m tox -e py312

Esto es útil cuando estás corrigiendo una falla específica de una versión.

32.11 Pasar argumentos a pytest

Podemos permitir que tox pase argumentos extra a pytest. Modifica tox.ini:

[testenv]
deps =
    pytest
commands =
    python -m pytest {posargs}

Ahora puedes ejecutar una prueba concreta:

python -m tox -e py312 -- tests/test_operaciones.py::test_sumar

32.12 Agregar cobertura

Si quieres medir cobertura dentro de tox:

[testenv]
deps =
    pytest
    pytest-cov
commands =
    python -m pytest --cov=calculadora --cov-report=term-missing {posargs}

tox instalará pytest-cov dentro de cada entorno antes de ejecutar las pruebas.

32.13 Usar extras o requirements

En proyectos simples puedes listar dependencias en deps. Si usas un archivo requirements-dev.txt:

[testenv]
deps =
    -r requirements-dev.txt
commands =
    python -m pytest {posargs}

Esto evita duplicar dependencias en varios lugares.

32.14 Configurar variables de entorno

Algunas pruebas necesitan variables de entorno. Puedes definirlas con setenv:

[testenv]
setenv =
    APP_ENV = test
    API_URL = https://example.test
deps =
    pytest
commands =
    python -m pytest

Estas variables existen dentro del entorno de tox.

32.15 Permitir comandos externos

tox espera comandos controlados dentro del entorno. Si necesitas ejecutar un comando externo, puede requerir configuración adicional. En general, para este curso alcanza con:

commands =
    python -m pytest

Usar python -m pytest evita depender de rutas concretas del ejecutable pytest.

32.16 Configuración equivalente en pyproject.toml

También puedes configurar tox en pyproject.toml:

[tool.tox]
legacy_tox_ini = """
[tox]
envlist = py310, py311, py312
skip_missing_interpreters = true

[testenv]
deps =
    pytest
commands =
    python -m pytest {posargs}
"""

Para empezar, tox.ini suele ser más directo y fácil de leer.

32.17 Archivo tox.ini recomendado

Para la práctica, puedes dejar tox.ini así:

[tox]
envlist = py310, py311, py312
skip_missing_interpreters = true

[testenv]
deps =
    pytest
    pytest-cov
commands =
    python -m pytest --cov=calculadora --cov-report=term-missing {posargs}

32.18 Cuándo usar tox

tox es especialmente útil cuando:

  • Tu proyecto debe funcionar en varias versiones de Python.
  • Quieres reproducir localmente lo que hará una integración continua.
  • Necesitas entornos aislados para pruebas, lint o comandos de calidad.
  • Quieres evitar que dependencias instaladas globalmente oculten problemas.

32.19 tox no instala Python por ti

tox crea entornos usando intérpretes disponibles en tu sistema. Si configuras py310 pero no tienes Python 3.10 instalado, ese entorno no podrá ejecutarse.

skip_missing_interpreters = true evita que la ejecución falle solo porque una versión no está instalada.

32.20 Errores frecuentes

  • Esperar que tox instale versiones de Python: tox usa intérpretes ya instalados.
  • Olvidar dependencias en deps: cada entorno de tox empieza aislado.
  • No usar {posargs}: limita la posibilidad de ejecutar pruebas específicas.
  • Ignorar fallas de una versión: puede haber incompatibilidades reales.
  • Configurar demasiados entornos al principio: empieza con lo necesario y amplía después.

32.21 Comandos usados en este tema

mkdir tox-demo
cd tox-demo
python -m pip install pytest tox
mkdir src
mkdir src\calculadora
mkdir tests
New-Item src\calculadora\__init__.py -ItemType File
New-Item src\calculadora\operaciones.py -ItemType File
New-Item tests\test_operaciones.py -ItemType File
python -m pytest
python -m tox
python -m tox -e py312
python -m tox -e py312 -- tests/test_operaciones.py::test_sumar

32.22 Qué debes recordar de este tema

  • tox ejecuta comandos en entornos aislados.
  • envlist define qué entornos se ejecutan.
  • deps instala dependencias dentro de cada entorno.
  • commands define qué se ejecuta.
  • {posargs} permite pasar argumentos extra.
  • tox necesita que las versiones de Python ya estén instaladas.

32.23 Conclusión

En este tema configuramos tox para ejecutar pruebas en distintas versiones de Python, definir dependencias, pasar argumentos a pytest y agregar cobertura.

En el próximo tema veremos cómo integrar pruebas con formateo y análisis estático básico.