Un Fixture, Tres Bases de Datos
Un decorador. Tres bases de datos. Cero tests extra. Escribiste una suite sólida contra SQLite porque es rápida y no necesita configuración — pero producción corre PostgreSQL, y la brecha entre ambas es exactamente donde se esconden los bugs. pytest cierra esa brecha con la parametrización de fixtures: marca un solo fixture con una lista de backends, y cada test que depende de él se multiplica en silencio.
El mecanismo
Un fixture de pytest acepta una lista params. Cuando la tiene, pytest llama al fixture una vez por parámetro y vuelve a ejecutar todo el conjunto de tests dependientes cada vez. El valor actual llega a través del fixture integrado request como request.param:
import pytest
from sqlalchemy import create_engine
DB_URLS = [
"sqlite://", # in-memory
"postgresql+psycopg2://app:secret@localhost/app_test",
"mysql+pymysql://app:secret@localhost/app_test",
]
@pytest.fixture(params=DB_URLS, ids=["sqlite", "postgres", "mysql"])
def db_engine(request):
engine = create_engine(request.param)
yield engine
engine.dispose()
def test_saves_order(db_engine):
... # escrito una vez, corre tres veces
test_saves_order aparece en el reporte como tres tests — test_saves_order[sqlite], [postgres], [mysql] — sin cambiar una línea del cuerpo del test. La documentación de pytest lo dice directamente: params es "una lista opcional de parámetros que causará múltiples invocaciones de la función fixture y de todos los tests que la usan".
Por qué la suite solo-SQLite te miente
SQLite es famosamente permisiva. Diferencias que pasan en local y explotan en producción:
| Comportamiento de SQLite | Comportamiento de PostgreSQL / MySQL |
|---|---|
Tipado débil — guarda "abc" en una columna INTEGER | Tipos estrictos; el insert falla |
LIKE insensible a mayúsculas para ASCII por defecto | En PostgreSQL, LIKE distingue mayúsculas |
| Manejo laxo de fechas (strings) | Tipos DATE/TIMESTAMP reales, errores reales |
ALTER TABLE limitado | DDL completo — las migraciones se comportan distinto |
Cualquiera de estas es un test verde en tu laptop y rojo en producción. Correr las mismas aserciones contra el motor real atrapa toda la clase de errores de una vez.
Reglas prácticas
- Parametriza el fixture, no los tests.
@pytest.mark.parametrizeen cada test duplica la lista de backends por todos lados;paramsen el fixture la declara una vez y aplica a toda la suite. - Nombra las corridas. Pasa
ids=["sqlite", "postgres", "mysql"]para que los fallos se lean comotest_x[postgres], notest_x[db_engine2]. - Amplía el scope para motores lentos.
@pytest.fixture(scope="session", params=...)construye cada engine una vez por corrida en lugar de una vez por test — solo asegúrate de que los tests limpien lo suyo (trunca las tablas en el teardown del fixture). - Selecciona un backend al iterar.
pytest -k sqlitecorre solo la variante rápida en memoria mientras estás en el ciclo editar-correr; CI corre las tres.
Jugadas avanzadas
Salta lo que no está instalado. Envuelve un parámetro en pytest.param(...) para adjuntarle marks — el mismo truco que la documentación muestra para fixtures parametrizados:
@pytest.fixture(params=[
"sqlite://",
pytest.param(PG_URL, marks=pytest.mark.skipif(not has_pg(), reason="no postgres")),
])
def db_engine(request):
...
En local la suite degrada con gracia a SQLite; en CI, donde los servicios existen, corren todos los backends.
Apila parametrización. Un fixture parametrizado se compone con @pytest.mark.parametrize en el test: 3 backends x 4 casos de entrada = 12 tests generados, todavía una sola función.
Apúntalo a servicios reales. Al cuerpo del fixture no le importa de dónde viene la URL — un servicio de docker compose, una variable de entorno o un contenedor descartable. El patrón es idéntico: un fixture, un request.param, N backends.
Recursos
- pytest — parametrizar fixtures (guía how-to)
- pytest — how-to de parametrize (
pytest.param, marks, ids) - Referencia de la API de pytest —
@pytest.fixture(params=..., ids=...) - SQLAlchemy — configuración de engines y URLs de bases de datos
¿Construyendo una funcionalidad con AI? Yeda AI diseña, audita y entrega sistemas LLM de producción.