Ejercicio 1 — Vista operada
After esperado (la vista entera):
@api_view(["POST"])
def reserve_view(request):
serializer = ReserveInputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
reservation = reservar(request.user, **serializer.validated_data)
except DomainError as exc:
return problem(exc) # 13
return ReservationOut(reservation, status=201)~15 líneas vs 150: el HTTP es traducción, el dominio vive en el servicio, y manage.py shell >>> reservar(u,...) funciona sin request — la prueba del algodón. Si falló: queda algún request/Response escondido en el servicio (retíralo: inyecta datos, no HTTP).
Ejercicio 2 — Clock
class FakeClock:
def __init__(self, fixed: datetime): self._fixed = fixed
def now(self) -> datetime: return self._fixed
def test_reserva_expira_diez_minutos_despues():
clock = FakeClock(datetime(2027, 3, 1, 23, 59, tzinfo=dt.UTC))
r = reservar(user=self.buyer, event_uuid=..., seat_refs=[...], clock=clock)
self.assertEqual(r.expires_at, datetime(2027, 3, 2, 0, 9, tzinfo=dt.UTC))Sin sleeps ni freezegun-patch: la dependencia entra por parámetro y el test es determinista. El job de la 29 usará el mismo Clock para "ahora" — un solo lugar donde el tiempo es negociable en tests.
Ejercicio 3 — L
- El test que la caza:
def test_gateway_siempre_responde_o_lanza(self):
with self.assertRaises((GatewayError, ChargeResult)):
self.broken_gateway.charge(Amount(50), intent)Si la implementación rota devuelve None, assertRaises falla (no lanzó) y el tipo no coincide: la sustituibilidad rota en tests antes que en producción — ese es el valor de L con Protocolos + tests de contrato por implementación.
- Contrato docstring: "Devuelve ChargeResult(status=SUCCEEDED|PENDING). Lanza GatewayError en fallo temporal y DomainConflict en rechazo de negocio. Garantiza idempotencia por intent.id (14)". Con typing estricto (
-> ChargeResult) mypy caza el None en estático: docstring + types = contrato honesto.
Ejercicio 4 — O
PAYMENT_METHODS: dict[str, type[PaymentMethod]] = {}
def register(name: str):
def deco(cls):
PAYMENT_METHODS[name] = cls
return cls
return deco
@register("gift_card")
class GiftCardMethod(PaymentMethod): ...
@register("cash_on_delivery")
class CodMethod(PaymentMethod): ...Añadir "crypto" = 1 archivo nuevo con su clase + registro; el servicio itera PAYMENT_METHODS[name] sin conocer implementaciones. El if/elif habría tocado N lugares (violación O y riesgo de merge). Nota fina: el registro explícito evita los auto-imports mágicos que confunden.
Ejercicio 5 — I
- Primera tentativa: una interfaz "ReservationRepository" con 14 métodos. El endpoint público usa 1 (leer disponibilidad); el checkout, 3. Interfaces por consumidor:
AvailabilityReader.read(event_id) -> list[SeatFree]yReservationWriter.create(...)— nadie depende de métodos que no usa; los fakes de test son diminutos. - El utils.py se divide por dominio; la regla preventiva: "utils es solo funciones puras sin estado; si necesita config o I/O, es un servicio".
Resumen del profesor
- Las capas se prueban con la llamada sin request; los principios, con tests que fallan cuando los violas.
- Clock, gateway y cache: las primeras dependencias que valen inyectar (tiempo, dinero, red = lo no determinista).
- Protocol + default de producción: DI en Python sin contenedores; el fake de test es la documentación viviente.
- O se demuestra añadiendo un método de pago sin tocar el servicio: 1 archivo nuevo, 0 modificados.
Después: Lección 25 — Patrones de servicio.