Módulo 8: Proyecto: contrato + integración de Reservo

1. Presentación del módulo: el proceso completo de Reservo

Descripción

Llegaste al final de la guía, y este módulo es donde todo se junta. Durante siete módulos aprendiste las piezas una por una: qué distingue una prueba unitaria de una de integración (módulo 1), cómo un doble puede mentir en verde (módulo 2), cómo un contrato consumer-driven fija el comportamiento que dos componentes se prometen (módulo 3), cómo verificarlo desde los dos lados y cazar un cambio incompatible (módulo 4), cómo integrar componentes reales cruzando la costura (módulo 5), cómo probar en las fronteras de la base de datos, el archivo y el HTTP (módulo 6), y cómo aislar las pruebas de integración para que cada una empiece limpia (módulo 7). Cada pieza la practicaste sola. Ahora vas a ejecutar el proceso entero, de principio a fin, sobre Reservo, y a entregar tres cosas que trabajan juntas: un contrato, su verificación desde ambos lados, y una integración de punta a punta.

Y hay un cambio de foco que define este módulo: aquí no se evalúa cuántos tests escribes, sino el método. La pregunta que el capstone te enseña a responder no es "¿cuántas aserciones puse?", sino "¿qué doblé, qué dejé real, y por qué?". Un ingeniero que escribe doscientos tests sin saber cuál costura merece un contrato y cuál merece una integración tiene doscientos tests y ningún criterio. Uno que escribe seis tests sabiendo exactamente por qué cada uno existe —este contrato porque aquí el fake puede divergir, esta integración porque aquí el flujo real desencadena un bug que la inspección no ve— tiene una suite que dice la verdad. El capstone es la prueba de que tienes ese criterio. Por eso el corazón de esta lección, más que cualquier test, es una tabla: qué doblar y qué probar real.

Conexión con el módulo: esta lección es el mapa del capstone. Aquí no vas a escribir todavía la entrega —eso es el trabajo de las lecciones 2 a 7, que la arman paso a paso—; vas a ver el proceso completo de una sola mirada y a fijar el criterio que lo gobierna. Conocerás los tres entregables y cómo encajan, la tabla de decisión que resume la regla de oro de toda la guía, y verás, con salida real de pytest, la entrega terminada corriendo en verde: el contrato certificando el fake y el real, y la integración de punta a punta ejercitando bookgetcancel. Con ese destino claro, cada lección siguiente construye una parte de él hasta que, en la lección 8, lo entregas formalmente y cerramos la guía.

Analogía: la recepción de obra

En el módulo 1 usamos la imagen de la maqueta y el edificio: cada pieza, aislada, cumple su norma, pero el edificio se cae en las juntas. Cerremos la guía con la misma obra, ya terminada. Antes de entregar un edificio y dejar que entre gente, no basta con que el arquitecto jure que cada viga y cada columna se calcularon bien. Se hace una recepción de obra: una inspección final, con el edificio completo y ensamblado, donde se verifican las cosas que solo existen cuando todo está junto. Se abre la llave y se comprueba que el agua de verdad corre por las tuberías reales hasta la cocina; se carga la estructura y se mira que las juntas aguanten; se enciende el tablero y se ve que la corriente llega a cada enchufe. La recepción no vuelve a calcular la resistencia de una viga —eso ya se hizo, pieza por pieza—; verifica que las piezas, unidas, funcionan como sistema.

Este capstone es la recepción de obra de Reservo. Durante siete módulos calculaste y probaste cada pieza. Ahora haces la inspección final del sistema completo, y usas para ello exactamente los dos instrumentos que la guía te dio. El contrato es el pliego de la recepción: la lista de lo que cada junta debe cumplir, verificada igual contra la pieza de mentira y la de verdad, para que nadie pueda entregar una tubería que "se ve bien" pero no lleva agua. La integración de punta a punta es abrir la llave y ver correr el agua: el flujo bookgetcancel recorriendo BookingService y el SqliteBookingRepository real, cruzando la costura como lo hará en producción. Al final firmas el acta —la salida verde de pytest— sabiendo que no certificaste piezas sueltas, sino un sistema que funciona junto.

Los tres entregables

El capstone te pide tres cosas, y no son tres tareas sueltas: son tres capas de la misma garantía. Conviene verlas juntas desde ahora, porque cada lección siguiente construye una.

Entregable 1 — El contrato consumer-driven. Una batería de tests parametrizada que fija el comportamiento que BookingService (el consumer) necesita del BookingRepository (el provider): guardar-y-leer devuelve la misma reserva, get de un id ausente lanza, save dos veces del mismo id actualiza sin duplicar, find_by_room devuelve solo las reservas de esa sala. Es el spec compartido del módulo 3, escrito desde las necesidades reales del consumer.

Entregable 2 — La verificación desde ambos lados. El mismo contrato corriendo, sin duplicar una línea, contra el FakeBookingRepository y contra el SqliteBookingRepository real —los dos verdes—. Esta es la garantía del módulo 4: si el fake y el real cumplen las mismas cláusulas, el fake no puede mentir sobre nada que el contrato cubra. Ocho casos: cuatro cláusulas por dos providers.

Entregable 3 — La integración de punta a punta. Una prueba que conecta BookingService con el SqliteBookingRepository real y ejercita el flujo completo bookgetcancel cruzando la costura, aislada con una fixture o un rollback para que empiece limpia. Es la integración del módulo 5 con el aislamiento del módulo 7. Caza lo que el contrato no ve: los bugs de uso, no de forma.

Los tres se necesitan. El contrato solo certifica cada pieza contra un spec, cláusula por cláusula; no ve la colaboración viva. La integración solo ve un camino concreto del flujo; no certifica cada cláusula contra cada provider. Juntos cubren las dos preguntas que esta guía existe para responder: ¿el fake miente? (contrato) y ¿las piezas reales funcionan juntas? (integración).

La tabla: qué doblar y qué probar real

Aquí está el criterio que gobierna todo el capstone, condensado. Ante cada colaborador de BookingService, la decisión de doblarlo o dejarlo real no es gusto: sigue una regla. Se dobla lo lento, lo no determinista y lo externo; se mantiene real la costura que estás probando.

ColaboradorEn el contratoEn la integraciónPor qué
BookingRepositoryReal (fake Y sqlite)Real (sqlite)Es la costura bajo prueba. El contrato la verifica en ambas implementaciones; la integración la cruza de verdad. Es lo único que no se dobla, porque es lo que se quiere probar.
PaymentGatewayDoblado (StubPaymentGateway)DobladoExterno: cobrar de verdad tocaría una tarjeta y un servicio de terceros. No es la costura que este proyecto prueba; se dobla para que sea gratis, rápido y sin efectos.
clockDoblado (FixedClock)DobladoNo determinista: el tiempo real cambia en cada corrida, y cancel calcula el reembolso según la anticipación. Congelarlo convierte cada ancla (6000/3000/0) en un dato fijo del test.
EmailSenderDoblado (SpyEmailSender)DobladoExterno y con efecto secundario: mandar un correo de verdad llenaría bandejas y dependería de la red. Se dobla; si hiciera falta, el spy anota los envíos sin mandarlos.

Lee la tabla como el resumen de la guía entera. La columna del porqué es lo que se evalúa en el capstone: no que hayas doblado el reloj, sino que sepas decir "lo doblé porque es no determinista y quiero elegir qué ancla ejerzo". El repositorio es la única fila que dice "real" en las dos columnas, y no por casualidad: es la costura donde vive el riesgo que esta guía enseña a cubrir —la divergencia entre el fake y lo real—, así que es lo que el contrato verifica y lo que la integración cruza. Todo lo demás se dobla, porque probar BookingService contra el repositorio no requiere cobrar tarjetas de verdad ni esperar a que el reloj avance.

Hay una elección más, de nivel superior, que la tabla no muestra y que la lección 2 desarrolla: cuál costura merece un contrato. Reservo tiene dos costuras candidatas —el repositorio y el gateway de pagos—. El capstone elige el repositorio, porque es donde la divergencia fake-vs-real es más rica y peligrosa (la serialización del datetime, el get que lanza o devuelve None, el commit que persiste o no). Elegir dónde poner el esfuerzo es parte del método.

Un vistazo a la entrega terminada

Antes de construirla pieza por pieza, veamos el destino: la entrega completa corriendo en verde. Son los dos archivos que resumen el capstone —el contrato parametrizado (entregables 1 y 2) y la integración de punta a punta (entregable 3)— corridos juntos. No escribas esto aún; es el mapa, no el territorio. Lo armaremos con calma en las lecciones siguientes.

python3 -m pytest tests/test_repository_contract.py tests/test_end_to_end_integration.py -v

Qué esperar. En mi máquina (Python 3.14.0, pytest 9.1.1):

============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0
collected 10 items

tests/test_repository_contract.py::test_save_then_get_returns_the_same_booking[fake] PASSED [ 10%]
tests/test_repository_contract.py::test_save_then_get_returns_the_same_booking[sqlite] PASSED [ 20%]
tests/test_repository_contract.py::test_get_of_a_missing_id_raises[fake] PASSED [ 30%]
tests/test_repository_contract.py::test_get_of_a_missing_id_raises[sqlite] PASSED [ 40%]
tests/test_repository_contract.py::test_saving_the_same_id_twice_updates_not_duplicates[fake] PASSED [ 50%]
tests/test_repository_contract.py::test_saving_the_same_id_twice_updates_not_duplicates[sqlite] PASSED [ 60%]
tests/test_repository_contract.py::test_find_by_room_returns_only_that_rooms_bookings[fake] PASSED [ 70%]
tests/test_repository_contract.py::test_find_by_room_returns_only_that_rooms_bookings[sqlite] PASSED [ 80%]
tests/test_end_to_end_integration.py::test_book_then_get_persists_the_booking PASSED [ 90%]
tests/test_end_to_end_integration.py::test_book_cancel_get_full_flow_against_real_sqlite PASSED [100%]

============================== 10 passed in 0.01s ===============================

Diez verdes, y cada uno es una parte de la entrega. Los primeros ocho son el contrato desde ambos lados (entregables 1 y 2): cuatro cláusulas, cada una corrida contra [fake] y contra [sqlite], el fake y el real certificados iguales. Los dos últimos son la integración de punta a punta (entregable 3): bookget guardando y releyendo una reserva, y el flujo completo bookcancelget cruzando la costura contra SQLite de verdad. Esta salida es, literalmente, el acta de recepción de Reservo firmada. Todo lo que viene en las próximas seis lecciones es aprender a producir cada línea de este reporte —con criterio, no de memoria— y a leerlo cuando algo se ponga rojo.

El mapa de las ocho lecciones

El capstone se construye en orden, cada lección un ladrillo de la entrega:

LecciónQué construyesEl entregable
1El proceso completo y la tabla de decisión (esta)El criterio
2El contrato consumer-driven, cláusula por cláusulaEntregable 1
3La verificación contra el fake, y por qué no basta solaEntregable 2 (lado uno)
4La verificación contra el SQLite real, los dos lados en verdeEntregable 2 (completo)
5La integración de punta a punta bookgetcancelEntregable 3
6El aislamiento con fixture y rollbackEntregable 3 (robusto)
7La caza de un breaking change con el contratoLa prueba de valor
8El enunciado formal, la rúbrica y el cierre de la guíaLa entrega

Fíjate en que la lección 7 no añade un entregable nuevo: demuestra por qué valió la pena construir los otros tres, cazando en tu máquina un cambio que sin contrato explotaría en producción. Y la lección 8 no solo recoge la entrega: cierra la guía entera, con el resumen de los ocho módulos y el mapa de a dónde seguir en el ecosistema de Testing.

Errores comunes

Medir el capstone por cantidad de tests en vez de por método. Qué pasa: alguien escribe treinta tests de BookingService creyendo que más es mejor, y deja fuera la decisión de qué doblar o el contrato desde ambos lados. Por qué pasa: la cantidad es visible y fácil de producir; el criterio es invisible. Cómo detectarlo: si no puedes señalar, para cada test, qué costura prueba y por qué doblaste lo que doblaste, tienes volumen sin método. Cómo corregirlo: el capstone pide tres entregables bien elegidos, no una montaña de aserciones. Un contrato de cuatro cláusulas verificado desde ambos lados y una integración de punta a punta bien aislada valen más que cincuenta unit tests contra el fake. Se evalúa que sepas decir por qué existe cada prueba.

Creer que el contrato hace innecesaria la integración, o al revés. Qué pasa: alguien entrega solo el contrato ("ya verifico ambos providers") o solo la integración ("ya cruzo la costura real"), pensando que uno cubre al otro. Por qué pasa: los dos dan confianza, y es tentador quedarse con uno. Cómo detectarlo: pregúntate qué bug se te escaparía. Sin integración, un bug de uso que el contrato no enumeró (el datetime que cancel no puede restar) pasa. Sin contrato, una divergencia en una cláusula que la integración no ejercitó (el find_by_room de una sala vacía) pasa. Cómo corregirlo: entrega los dos. El contrato certifica cada pieza contra el spec; la integración verifica la colaboración viva. Son capas distintas de la misma garantía, y el capstone pide las dos a propósito.

Doblar la costura que se quiere probar. Qué pasa: alguien, por costumbre de escribir unit tests, dobla también el repositorio en la prueba de integración, dejando FakeBookingRepository en todos lados. Por qué pasa: doblar todo es el reflejo del testing unitario, y se hace sin pensar. Cómo detectarlo: si en tu "integración" ninguna pieza real cruza la costura, no es una integración —es un unit test con otro nombre—. Cómo corregirlo: la regla de la tabla es exacta: se dobla lo lento, lo no determinista y lo externo, pero se mantiene real la costura que estás probando. En este capstone esa costura es el repositorio; doblarlo vacía de sentido la integración. Real el repositorio, doblado todo lo demás.

Ejercicios

Ejercicio 1 — Llena la columna del porqué. Para cada colaborador de BookingService en la prueba de integración, di si va doblado o real y da la razón en una frase: (a) el SqliteBookingRepository; (b) el StubPaymentGateway; (c) el FixedClock; (d) el SpyEmailSender.

Ver solución
  • (a) SqliteBookingRepository — real. Es la costura que la integración prueba. Todo el punto del test es ver a BookingService y al repositorio de verdad funcionar juntos cruzando la costura; doblarlo eliminaría lo único que se quiere verificar.
  • (b) StubPaymentGateway — doblado. El pago es un colaborador externo: cobrar de verdad tocaría una tarjeta y un servicio de terceros. No es la costura bajo prueba, así que se dobla para que el test sea gratis, rápido y sin efectos reales.
  • (c) FixedClock — doblado. El reloj es no determinista: la hora real cambia en cada corrida. cancel calcula el reembolso según la anticipación al inicio, así que congelar el reloj es lo que convierte cada ancla (6000, 3000, 0) en un dato fijo y repetible del test.
  • (d) SpyEmailSender — doblado. El correo es externo y con efecto secundario: mandar un email de verdad llenaría bandejas y dependería de la red. Se dobla; el spy anota los envíos sin mandarlos, por si el test necesita verificar que se avisó.

El patrón que estás aplicando es la regla de oro: real la costura que pruebas (el repositorio), doblado lo lento, lo no determinista y lo externo (el resto). Saber decir la razón de cada casilla es exactamente lo que el capstone evalúa.

Ejercicio 2 — Qué bug caza cada entregable. Empareja cada bug de Reservo con el entregable que lo cazaría, y explica por qué el otro lo dejaría pasar: (a) el SqliteBookingRepository.get de un id ausente empieza a devolver None; (b) el SqliteBookingRepository.get devuelve el start como str y cancel no puede restarlo.

Ver solución
  • (a) El get que devuelve None lo caza el contrato (entregable 1 y 2). La cláusula "get de un id ausente lanza", corrida contra el real, se pone roja en cuanto el provider deja de lanzar: test_get_of_a_missing_id_raises[sqlite] falla con DID NOT RAISE KeyError. Es una divergencia de comportamiento en un caso enumerado, justo lo que un contrato cubre. Una integración del flujo feliz (bookcancelget con ids que sí existen) podría no ejercitar nunca el id ausente, y dejarlo pasar.
  • (b) El start que vuelve como str lo caza la integración (entregable 3). cancel usa el start en una resta de fechas, y un str no se resta de un datetime: el flujo revienta con un TypeError. La integración lo caza por el uso, sin que ninguna aserción apunte al start. Un contrato incompleto —que verifica status y price_cents pero olvidó comparar el start— lo dejaría pasar en verde, porque nunca miró el campo que diverge.

La moraleja del emparejamiento es la razón por la que el capstone pide los dos entregables: el contrato caza las divergencias que enumeras en cláusulas; la integración caza las que el uso real desencadena aunque no las hayas enumerado. Cada uno tiene un punto ciego que el otro cubre.

Ejercicio 3 — Elige la costura del contrato. Reservo tiene dos costuras candidatas para un contrato: el BookingRepository y el PaymentGateway. El capstone elige el repositorio. Da dos razones por las que el repositorio es la mejor elección para practicar contract testing, y una situación en la que el gateway sí merecería su propio contrato.

Ver solución

Dos razones para elegir el repositorio:

  1. Es donde la divergencia fake-vs-real es más rica. El SqliteBookingRepository serializa (datetime→texto), tiene transacciones (el commit), lanza o no según encuentre la fila, y actualiza con ON CONFLICT. Cada una de esas conductas es una oportunidad de que el fake en memoria diverja del real. El contrato del repositorio, por tanto, protege de bugs concretos y variados —exactamente los que la guía ha ido cazando—.
  2. Tiene dos implementaciones reales que deben coincidir. El proyecto ya tiene un FakeBookingRepository (para unit tests rápidos) y un SqliteBookingRepository (para producción). Un contrato es la herramienta precisa para mantener a esos dos honestos entre sí. Donde hay un doble y un real que se usan de verdad, hay una necesidad concreta de contrato.

Cuándo el gateway merecería su propio contrato: si PaymentGateway tuviera, como el repositorio, un doble usado en los tests (StubPaymentGateway) y una implementación real contra un servicio de pagos concreto, y el consumer dependiera de comportamientos sutiles de ese servicio —por ejemplo, "un cobro rechazado lanza PaymentDeclined, no devuelve None", o "un refund de más que el cobro original lanza"—. En cuanto el stub y el real puedan divergir en un comportamiento del que BookingService depende, el gateway reclama su propio contrato, escrito desde las necesidades del consumer igual que el del repositorio. La técnica es la misma; lo que cambia es qué costura corre el riesgo de divergir en tu sistema.

Resumen y siguiente paso

En esta lección viste el capstone completo antes de construirlo. Entendiste que teje los siete módulos en un solo proceso sobre Reservo, con tres entregables que son tres capas de la misma garantía: el contrato consumer-driven (entregable 1), su verificación desde ambos lados contra el fake y el real (entregable 2), y la integración de punta a punta bookgetcancel aislada (entregable 3). Fijaste el criterio que gobierna todo con la tabla de qué doblar y qué probar real —real la costura que pruebas, el repositorio; doblado lo lento, lo no determinista y lo externo, el pago, el reloj y el correo— y aprendiste que lo que se evalúa es el método, no la cantidad. Y con la recepción de obra viste que un capstone no re-calcula piezas: verifica que el sistema, ensamblado, funciona. Cerraste con un vistazo a la entrega terminada: diez verdes, el contrato y la integración corriendo juntos.

Antes de avanzar deberías poder: nombrar los tres entregables y qué bug caza cada uno; aplicar la regla de qué doblar y qué mantener real a cada colaborador de BookingService, con la razón; y explicar por qué el repositorio, y no el gateway, es la costura que este capstone elige para el contrato.

Con el mapa claro, empieza la construcción. En la lección 2 escribes el primer entregable: el contrato consumer-driven del BookingRepository. Vas a derivar cada una de las cuatro cláusulas de una necesidad concreta de BookingService —no del catálogo del provider— y a montar la batería con la fixture parametrizada que la correrá, en las lecciones siguientes, contra el fake y contra el real. El proceso completo empieza por escribir el pliego de la recepción.

Recursos