Módulo 7 · Pruebas

Lección 34 — Pruebas de integración

Bases de datos reales en contenedores: nada de SQLite fingiendo ser Postgres.

Publicada
En esta lección
  1. Ejercicio 1 — El entorno de test
  2. Ejercicio 2 — Un asiento, un ganador
  3. Ejercicio 3 — Las costuras del outbox y del ledger
  4. Ejercicio 4 — Aislamiento
  5. Ejercicio 5 — La tabla de garantías
  6. Entrega

Postgres real en contenedor. Sin solutions.md hasta entregar.

Ejercicio 1 — El entorno de test

  1. Levanta el docker-compose.test.yml (Postgres 16 en 5433 + Redis en 6380) y corre la suite de integración con DATABASE_URL apuntando ahí. Pega el comando completo (env var + pytest).
  2. Miente a propósito: corre el test del índice parcial del outbox (25) contra SQLite (override_settings o BD de test SQLite). ¿Qué pasa y por qué? Documenta la mentira concretamente (¿el test pasa y miente, o falla honesto?).
  3. Añade el check de versión al conftest: si la BD de test no es Postgres 16, pytest.fail al arrancar con mensaje claro (la 27: fail-fast).

Ejercicio 2 — Un asiento, un ganador

  1. Implementa el test de concurrencia (ThreadPoolExecutor + transaction=True) con N=2, y luego N=10 threads sobre el MISMO asiento: exactamente 1 ganador, 9 SeatUnavailable.
  2. Mide: ¿cuánto tarda el test con N=10? Anota el número como línea base (la 36 lo comparará bajo carga).
  3. La variante con nowait (10): misma prueba con select_for_update(nowait=True) — los perdedores fallan INMEDIATAMENTE con LockNotAvailable. ¿Qué UX de checkout prefieres y por qué? (2 líneas: espera-block vs rechazo-reintento).

Ejercicio 3 — Las costuras del outbox y del ledger

  1. El test de atomicidad del outbox con TX REAL: fuerza el fallo en la mitad de la transacción de reservar() (trigger/constraint que explote) y verifica cero eventos publicados. Sin mocks de transaction: la BD decide.
  2. El test de la constraint del ledger (31): inserta duplicado (org, mes) y verifica IntegrityError — y que el código de liquidar_mes lo convierte en skip, no en crash (el get_or_create).
  3. El test de la saga reanudable (32): crea saga PENDING con intent_id, "mata" el proceso (simula con excepción no capturada), reanuda, y verifica UN solo cargo en la pasarela fake con Idempotency-Key.

Ejercicio 4 — Aislamiento

  1. Rompe el aislamiento a propósito: un test que escribe en Redis DB 0 (la del dev) y otro que depende de esa clave. Corre solo el segundo: ¿rojo? Corre la suite completa: ¿verde por orden? Documenta el pestilencia y arréglalo (prefijo + DB 15).
  2. Corre la suite de integración con pytest -p no:randomly y luego con randomización (pytest-randomly): ¿algún test dependiente afloró? El test que solo pasa en orden: arréglalo o elícitalo.
  3. La regla final del conftest: autouse fixture que verifica que NINGÚN test escribe en la BD de desarrollo (la URL debe contener tf_test). ¿Cómo lo implementarías con un guard en el fixture?

Ejercicio 5 — La tabla de garantías

  1. Escribe la tabla de garantías (§5) para TU proyecto con el estado real: cada fila con su test (o "falta"). ¿Cuántas garantías vendidas al negocio no tienen póliza?
  2. Implementa la que falta más crítica (elige: advisory lock, UniqueConstraint del ledger, o saga reanudable) y pégala.
  3. Corre la suite completa y cronometra por capa: unitarias X s, integración Y s, e2e Z s. ¿Cumple el presupuesto (10s / 2min / 10min)? Si no: ¿qué test está en la capa equivocada?

Entrega

Pega el compose de test, el test N=10 con sus ms, el test de atomicidad del outbox y la tabla de garantías con huecos. Después: Lección 35 — Pruebas E2E y de contrato.