Módulo 7 · Pruebas

Lección 33 — Pruebas unitarias bien hechas

pytest, mocks sin abusar y pruebas que sobreviven al refactor.

Publicada
En esta lección
  1. Ejercicio 1 — La auditoría de la suite
  2. Ejercicio 2 — La parametrización que sobrevive
  3. Ejercicio 3 — Fakes antes que mocks
  4. Ejercicio 4 — Los test del CÓMO, cazados
  5. Ejercicio 5 — La convención del proyecto
  6. Entrega

pytest sobre los servicios de TicketFlow. Sin solutions.md hasta entregar.

Ejercicio 1 — La auditoría de la suite

  1. Corre pytest --durations=10 (o manage.py test): lista los 10 test más lentos. Clasifica cada uno: unitaria legítima / integración disfrazada / test del tiempo real.
  2. Caza los anti-patrones en TU suite: un test que depende de otro (corre el archivo completo vs test a test), un assertion de mensaje literal, un sleep, un mock de ORM. Documenta cada hallazgo con el nombre del test.
  3. Migra 3 tests de la suite vieja (manage.py test → pytest): el setUp → fixture, y uno de ellos parametrizado. Verifica que la suite entera sigue en verde con ambos runners.

Ejercicio 2 — La parametrización que sobrevive

  1. Escribe test_calcular_total con 6 casos parametrizados (1 asiento, 3, 10, 0 [error], -1 [error], 1000 [límite]) — tabla de casos arriba, assertion única abajo.
  2. El test debe sobrevivir a DOS refactors: renombra calcular_total → precio_por y cambia su firma interna (extrae _descuento_volumen). Los tests NO se editan. ¿Cuántos murieron? (esperado: 0 si probabas el QUÉ).
  3. Añade el caso de la comisión del org (la 08) con parametrize sobre pytest.param(..., id="org-10-pct") — los ids legibles en el reporte.

Ejercicio 3 — Fakes antes que mocks

  1. Escribe el fixture gateway_rechaza (FakeGateway configurable: declina_todo, timeout_pero_cobra — el de la 32) y reescribe 3 tests que maqueaban la pasarela con patch. ¿Cuántas líneas se ahorraron y qué assertion nueva te permitió (el estado de la saga)?
  2. El test del repositorio: reescribe el test de reserva duplicada (10) con InMemoryReservationRepository (25) SIN BD. ¿En cuántos ms corre? (midelo con --durations).
  3. El único mock legítimo: verifica que enviar_email_confirmacion.delay se ENCOLA vía on_commit (la interacción ES el contrato aquí). Escribe el test con captureOnCommitCallbacks y el mock de la tarea.

Ejercicio 4 — Los test del CÓMO, cazados

  1. Escribe un test MALO a propósito: with patch("reservations.services.Seat.objects") as mock_seat:... mock_seat.filter.assert_called_once(). Ahora refactoriza reservar() (usa select_for_update en vez de filter): el test bueno sigue verde, el malo muere. Pega la evidencia de ambos.
  2. El mensaje literal: encuentra un test con assertion de texto completo y cámbialo a err.code. Verifica: cambia el texto del error humano → test verde.

Ejercicio 5 — La convención del proyecto

  1. Escribe el conftest.py del proyecto con las 5 fixtures base (clock_fijo, evento, comprador, gateway, mailbox) y documenta cada una con docstring de una línea.
  2. Renombra 5 tests a la convención test_<sujeto>_<escenario>_<resultado> y corre la suite: pytest -k "reservar" debe encontrarlos todos por keyword.
  3. Mide la cobertura de services/: pytest --cov=reservations.services --cov-report=term. ¿Qué servicio está por debajo del 90% y qué línea NO está probada? (la respuesta no es "añade test": es "¿esa línea es código muerto?").

Entrega

Pega el reporte de durations, los 3 tests migrados, el antes/después de los mocks→fakes y la cobertura de servicios. Después: Lección 34 — Pruebas de integración.