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.
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
Conviene usar conftest.py cuando una fixture será utilizada por varios archivos de prueba.
Casos típicos:
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.
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.
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.
Ejecuta:
python -m pytest tests/test_usuarios.py
Si la fixture está bien ubicada y el nombre coincide, las pruebas deberían pasar.
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
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.
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.
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"
No todas las fixtures deben ir a conftest.py. Si una fixture se usa en un solo archivo, puede quedarse en ese archivo.
conftest.py.conftest.py en esa subcarpeta.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.
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.
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
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.
Si conftest.py crece demasiado, puede volverse difícil de mantener. Algunas recomendaciones:
tests/helpers si la construcción se vuelve compleja.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.
conftest.py esté en la carpeta correcta.Mueve fixtures repetidas a tests/conftest.py:
usuario_validousuario_inactivoproductos_carritoLuego crea dos archivos que usen esas fixtures sin importarlas manualmente: tests/test_usuarios.py y tests/test_carrito.py.
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
Antes de continuar con el próximo tema, verifica lo siguiente:
tests/conftest.py.conftest.py.autouse=True salvo que esté justificado.tests/helpers.python -m pytest.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.