13. Uso de conftest.py para compartir configuración y fixtures

13.1 Objetivo del tema

En el tema anterior creamos fixtures dentro de un archivo de prueba. Eso funciona bien cuando la fixture se usa en un solo archivo, pero no es ideal cuando varias pruebas necesitan la misma preparación.

En este tema usaremos conftest.py, un archivo especial de pytest que permite compartir fixtures y configuración entre archivos de prueba sin importarlas manualmente.

Objetivo práctico: mover fixtures reutilizables a conftest.py y usarlas desde varios archivos de prueba.

13.2 Qué es conftest.py

conftest.py es un archivo que pytest detecta automáticamente. Las fixtures definidas allí quedan disponibles para las pruebas ubicadas en la misma carpeta y en sus subcarpetas.

No hace falta importar esas fixtures en cada archivo de prueba. pytest las descubre por nombre.

Ejemplo de ubicación:

tests/
|-- conftest.py
|-- test_carrito.py
|-- test_usuarios.py
`-- test_textos.py

13.3 Cuándo usar conftest.py

Conviene usar conftest.py cuando una fixture será utilizada por varios archivos de prueba.

Casos típicos:

  • Datos comunes, como usuarios válidos o productos de ejemplo.
  • Carpetas temporales preparadas de una forma específica.
  • Configuración de pruebas compartida.
  • Objetos base que aparecen en muchas pruebas.
  • Preparación y limpieza común para una sección de la suite.

13.4 Crear tests/conftest.py

Crea el archivo tests/conftest.py:

type nul > tests\conftest.py

En Linux o macOS:

touch tests/conftest.py

Este archivo quedará dentro de la carpeta tests, junto a los archivos de prueba.

13.5 Mover una fixture a conftest.py

Agrega esta fixture en tests/conftest.py:

import pytest


@pytest.fixture
def usuario_valido():
    return {
        "nombre": "Ana",
        "email": " ANA@EXAMPLE.COM ",
        "activo": True,
    }

Ahora cualquier prueba dentro de tests puede recibir usuario_valido como parámetro.

13.6 Usar la fixture compartida

Crea o modifica tests/test_usuarios.py:

from app.usuarios import obtener_email, usuario_esta_activo


def test_usuario_esta_activo_con_usuario_valido_devuelve_true(usuario_valido):
    assert usuario_esta_activo(usuario_valido) is True


def test_obtener_email_con_usuario_valido_devuelve_email_normalizado(usuario_valido):
    assert obtener_email(usuario_valido) == "ana@example.com"

Observa que no importamos usuario_valido. pytest la encuentra automáticamente en conftest.py.

13.7 Ejecutar la prueba

Ejecuta:

python -m pytest tests/test_usuarios.py

Si la fixture está bien ubicada y el nombre coincide, las pruebas deberían pasar.

13.8 Compartir fixtures de carrito

Agrega también una fixture para productos en tests/conftest.py:

@pytest.fixture
def productos_carrito():
    return [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

Luego úsala en tests/test_carrito.py:

from app.carrito import calcular_total


def test_calcular_total_con_productos_de_fixture_devuelve_suma_total(productos_carrito):
    assert calcular_total(productos_carrito) == 350

13.9 Alcance por carpetas

Las fixtures de un conftest.py están disponibles para pruebas en su carpeta y subcarpetas.

tests/
|-- conftest.py
|-- test_general.py
`-- unit/
    `-- test_unidad.py

En este caso, las pruebas dentro de tests/unit también pueden usar las fixtures de tests/conftest.py.

13.10 conftest.py en subcarpetas

También puedes tener un conftest.py específico en una subcarpeta:

tests/
|-- conftest.py
`-- unit/
    |-- conftest.py
    `-- test_unidad.py

El archivo de la subcarpeta puede definir fixtures específicas para esa zona de la suite. Úsalo cuando realmente haya una preparación propia de esa sección.

13.11 No importar fixtures desde conftest.py

Una práctica incorrecta sería:

from tests.conftest import usuario_valido

No hace falta hacerlo. pytest se encarga de descubrir las fixtures de conftest.py. Las pruebas solo deben recibirlas como parámetro:

def test_obtener_email_con_usuario_valido_devuelve_email_normalizado(usuario_valido):
    assert obtener_email(usuario_valido) == "ana@example.com"

13.12 Fixtures locales y fixtures compartidas

No todas las fixtures deben ir a conftest.py. Si una fixture se usa en un solo archivo, puede quedarse en ese archivo.

  • Fixture usada por un solo archivo: definirla en el archivo de prueba.
  • Fixture usada por varios archivos: moverla a conftest.py.
  • Fixture usada por una subcarpeta concreta: considerar un conftest.py en esa subcarpeta.

13.13 Fixture con yield en conftest.py

También podemos usar yield en fixtures compartidas:

@pytest.fixture
def archivo_temporal_configuracion(tmp_path):
    ruta = tmp_path / "config.txt"
    ruta.write_text("ambiente=local", encoding="utf-8")

    yield ruta

    if ruta.exists():
        ruta.unlink()

En muchos casos tmp_path ya limpia los archivos temporales, pero el ejemplo muestra cómo ubicar preparación y limpieza en una fixture compartida.

13.14 Fixtures autouse

Una fixture con autouse=True se ejecuta automáticamente sin pedirla como parámetro.

@pytest.fixture(autouse=True)
def mostrar_separador():
    print("Inicio de prueba")

Debe usarse con cuidado. Si una fixture se ejecuta sin estar visible en la firma de la prueba, puede ser más difícil entender qué preparación está ocurriendo.

13.15 Cuándo evitar autouse

Evita autouse=True si la fixture modifica estado importante, cambia variables de entorno o prepara datos que no todas las pruebas necesitan.

En general, para aprender y mantener claridad, es mejor que la prueba declare explícitamente lo que usa:

def test_calcular_total_con_productos_de_fixture_devuelve_suma_total(productos_carrito):
    assert calcular_total(productos_carrito) == 350

13.16 Ver fixtures disponibles

pytest puede listar fixtures disponibles:

python -m pytest --fixtures

La salida puede ser extensa, porque incluye fixtures propias y fixtures internas de pytest. Aun así, es útil para diagnosticar qué fixtures reconoce el proyecto.

13.17 Organizar conftest.py

Si conftest.py crece demasiado, puede volverse difícil de mantener. Algunas recomendaciones:

  • Agrupa fixtures relacionadas.
  • Usa nombres claros.
  • No guardes allí lógica de negocio.
  • No conviertas todo en fixture compartida.
  • Extrae helpers a tests/helpers si la construcción se vuelve compleja.

13.18 Combinar helpers y conftest.py

Una fixture puede usar un helper para construir datos. Por ejemplo, en tests/helpers/usuarios_helper.py:

def crear_usuario(nombre, email, activo=True):
    return {
        "nombre": nombre,
        "email": email,
        "activo": activo,
    }

Y en tests/conftest.py:

import pytest

from tests.helpers.usuarios_helper import crear_usuario


@pytest.fixture
def usuario_valido():
    return crear_usuario("Ana", " ANA@EXAMPLE.COM ", True)

Así conftest.py expone la fixture y el helper concentra la construcción.

13.19 Problemas frecuentes

  • Fixture no encontrada: revisa que conftest.py esté en la carpeta correcta.
  • Importaste una fixture desde conftest.py: elimina el import y recíbela como parámetro.
  • Hay muchas fixtures globales: mueve las específicas a archivos de prueba o subcarpetas.
  • Una fixture autouse genera confusión: hazla explícita si modifica estado importante.
  • conftest.py creció demasiado: usa helpers para construir datos complejos.

13.20 Ejercicio práctico

Mueve fixtures repetidas a tests/conftest.py:

  • usuario_valido
  • usuario_inactivo
  • productos_carrito

Luego crea dos archivos que usen esas fixtures sin importarlas manualmente: tests/test_usuarios.py y tests/test_carrito.py.

13.21 Solución propuesta

Archivo tests/conftest.py:

import pytest


@pytest.fixture
def usuario_valido():
    return {
        "nombre": "Ana",
        "email": " ANA@EXAMPLE.COM ",
        "activo": True,
    }


@pytest.fixture
def usuario_inactivo():
    return {
        "nombre": "Luis",
        "email": "luis@example.com",
        "activo": False,
    }


@pytest.fixture
def productos_carrito():
    return [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

Uso en una prueba:

from app.carrito import calcular_total


def test_calcular_total_con_productos_de_fixture_devuelve_suma_total(productos_carrito):
    assert calcular_total(productos_carrito) == 350

Ejecuta:

python -m pytest

13.22 Lista de verificación

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

  • Existe tests/conftest.py.
  • Las fixtures compartidas están en conftest.py.
  • Las pruebas usan fixtures compartidas sin importarlas.
  • Las fixtures locales siguen dentro del archivo que las usa si no son compartidas.
  • Evitas autouse=True salvo que esté justificado.
  • Los helpers complejos están en tests/helpers.
  • La suite completa se ejecuta con python -m pytest.

13.23 Conclusión

En este tema usamos conftest.py para compartir fixtures entre varios archivos de prueba. Esto reduce duplicación y mantiene la preparación común en un lugar conocido por pytest.

En el próximo tema trabajaremos con parametrización para ejecutar múltiples escenarios con poco código y sin duplicar pruebas casi idénticas.