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

3. Verificar el contrato contra el `FakeBookingRepository`

Descripción

Tienes el contrato escrito; ahora empieza a verificarlo, y empiezas por el lado más cómodo: el fake. En esta lección corres la batería contra el FakeBookingRepository y ves las cuatro cláusulas en verde. Es el primer lado del segundo entregable —la verificación desde ambos lados—, y es un paso real: confirma que el fake, el doble que usan todos tus unit tests rápidos, cumple las cuatro promesas que el consumer necesita. Un fake que fallara una cláusula del contrato sería un fake que miente, y lo sabrías aquí.

Pero esta lección tiene una segunda mitad, más importante que la primera, y es la que separa a quien entiende el capstone de quien solo lo copia: el verde contra el fake, solo, no prueba nada. Es necesario, sí; suficiente, no. Un contrato que solo has visto pasar contra el fake es un contrato que le preguntó al fake si el fake tiene razón, y el fake, que es la suposición bajo examen, siempre responde que sí. Esa es exactamente la mentira en verde del módulo 2. La verificación contra el fake certifica que tu contrato es coherente con el doble; no dice nada sobre si el doble coincide con lo real. Para eso falta cruzar la costura con el SqliteBookingRepository, que es la lección 4. Aquí ves el primer verde y aprendes a desconfiar de él lo justo.

Conexión con el módulo: esta lección entrega la primera mitad del entregable 2 y monta la tensión que la lección 4 resuelve. La batería que escribiste en la lección 2 corre aquí contra [fake] y en la 4 contra [fake] y [sqlite] juntos. El objetivo de hoy no es solo ver cuatro verdes: es entender por qué esos cuatro verdes, sin el otro lado, son una foto incompleta —el simulador confirmándose a sí mismo—. Con esa desconfianza instalada, el verde de ambos lados de la lección 4 tendrá el peso que merece.

Analogía: corregir tu propio examen con tu propia clave

Imagina que estudias para un examen y, para practicar, escribes tú mismo las preguntas y la clave de respuestas. Resuelves tu examen, te corriges con tu propia clave, y sacas cien. ¿Qué probaste? Que eres coherente contigo mismo: tus respuestas coinciden con lo que tú creíste que era correcto. No probaste que sepas la materia —si entendiste mal un tema, tu pregunta y tu respuesta comparten el mismo error, y tu autocorrección lo bendice—. El cien es real, pero mide una sola cosa: que no te contradijiste. Para saber si de verdad sabes, necesitas un examen que no escribiste tú, corregido contra una clave externa —el estándar oficial—.

Verificar el contrato contra el fake es corregirte con tu propia clave. El fake lo escribiste tú, con las mismas suposiciones que pusiste en el contrato; cuando el contrato pasa contra el fake, confirmas que tu doble es coherente con tus cláusulas, nada más. Si tu suposición sobre el repositorio estaba equivocada, el fake la comparte y el contrato la bendice: verde, y falso. El estándar externo —la clave oficial— es el SqliteBookingRepository real, la pieza que de verdad corre en producción, que no escribiste para pasar tu examen y que se comporta según SQLite, no según tus suposiciones. Por eso el verde contra el fake es el cien de tu autoexamen: necesario para seguir, pero no la prueba de que sabes. La prueba llega cuando el mismo contrato pasa contra el real.

Ejemplo trabajado: los cuatro casos [fake] en verde

Corramos la batería, pero solo el lado del fake. Pytest permite seleccionar casos por su id con -k, así que -k fake corre las cuatro cláusulas contra [fake] y deja fuera las de [sqlite]. Es la forma de mirar un solo lado del contrato:

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

python3 -m pytest tests/test_repository_contract.py -k fake -v
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0
collected 8 items / 4 deselected / 4 selected

tests/test_repository_contract.py::test_save_then_get_returns_the_same_booking[fake] PASSED [ 25%]
tests/test_repository_contract.py::test_get_of_a_missing_id_raises[fake] PASSED [ 50%]
tests/test_repository_contract.py::test_saving_the_same_id_twice_updates_not_duplicates[fake] PASSED [ 75%]
tests/test_repository_contract.py::test_find_by_room_returns_only_that_rooms_bookings[fake] PASSED [100%]

======================= 4 passed, 4 deselected in 0.01s ========================

Cuatro verdes, y cuatro deseleccionados. Pytest recolectó los ocho casos, corrió los cuatro [fake] y saltó los cuatro [sqlite] —eso dice 4 deselected—. Las cuatro cláusulas del contrato pasan contra el FakeBookingRepository: guarda y lee la misma reserva, lanza KeyError en un id ausente, actualiza en vez de duplicar, y lista solo la sala pedida. Es un resultado legítimo y útil: confirma que el fake que usan tus unit tests cumple el contrato del consumer. Si un compañero hubiera escrito el fake con .get() en vez de [...] —devolviendo None donde debe lanzar—, la cláusula 2 estaría aquí en rojo, y lo sabrías. El verde contra el fake caza un fake mal escrito.

Por qué este verde, solo, no basta

Y ahora la parte que importa. Ese verde certifica una cosa precisa, y solo esa: el fake es coherente con el contrato que tú escribiste. No dice nada sobre si el fake coincide con el SqliteBookingRepository real. Y esa es justo la pregunta que el contrato existe para responder.

Piénsalo con la divergencia que la guía persiguió desde el módulo 1: el datetime que el fake devuelve intacto y el real devuelve como texto. La cláusula 1 —"guardar-y-leer devuelve la misma reserva"— pasa contra el fake, porque el fake guarda el objeto entero y lo devuelve idéntico, datetime incluido. Contra el fake, repo.get("bk-1") == booking es verdad sin esfuerzo. Pero si el SqliteBookingRepository tuviera el bug de no reconstruir el datetime al leer, la misma cláusula fallaría contra [sqlite] —un Booking con start de texto no es igual a uno con start de datetime—. El verde de [fake] no vio ese bug, no porque la cláusula sea floja, sino porque el fake no serializa: el fake no puede exhibir el problema que solo el real tiene.

Aquí está la clave, y es la lección del módulo 2 hecha método: el fake es a la vez el sujeto y el oráculo. Cuando pruebas el contrato contra el fake, le preguntas al fake si el fake cumple una suposición que tú escribiste basándote en cómo creíste que se comporta el repositorio. Si tu suposición era falsa, el fake la comparte —lo escribiste con la misma cabeza— y el contrato la bendice en verde. El fake no puede contradecir la suposición porque el fake es la suposición. Por eso el verde contra el fake mide coherencia interna, no verdad. La verdad requiere una referencia externa: la pieza real.

Esto no degrada el paso que diste. Verificar el fake es necesario —un fake que falla el contrato es un problema que quieres ver—, y es la mitad del entregable 2. Lo que esta lección te pide entender es que es media prueba. La otra mitad, la que convierte "el fake es coherente conmigo" en "el fake coincide con lo real", es correr la misma batería contra [sqlite]. Ahí el contrato deja de ser tu autoexamen y se vuelve la comparación contra el estándar oficial.

Una regla práctica: nunca entregues un contrato de un solo lado

De esto sale una regla dura para el capstone, y para cualquier contract testing que hagas después: un contrato verificado contra una sola implementación no es un contrato, es un test de esa implementación. El poder del contrato viene de correr la misma batería contra dos o más providers; con uno solo, no hay comparación, y sin comparación no hay garantía de coincidencia. Si en tu entrega vieras solo 4 passed con ids [fake], tendrías un test del fake, no un contrato del repositorio.

La forma correcta —y la que la lección 4 hace— es no filtrar con -k: correr la batería entera, los ocho casos, y ver [fake] y [sqlite] uno al lado del otro. El -k fake de esta lección fue una lupa para mirar un lado y entender qué prueba y qué no; no es como se entrega el contrato. Lo tuviste que ver aislado una vez para desconfiar de él con conocimiento; a partir de la lección 4, siempre los dos lados juntos.

Errores comunes

Dar el contrato por verificado con el verde del fake. Qué pasa: se corre la batería contra el fake, salen cuatro verdes, y se concluye "el contrato pasa, listo". Por qué pasa: el verde se siente como una meta alcanzada, y el fake es lo más rápido de correr. Cómo detectarlo: pregúntate si alguna de esas cuatro cláusulas tocó el SqliteBookingRepository real. Si todas usaron el fake, no verificaste coincidencia con lo real —verificaste coherencia con tu doble—. Cómo corregirlo: el verde del fake es la mitad del entregable 2, no el entregable. No cierres hasta correr la misma batería contra [sqlite] y ver los dos lados verdes juntos, que es la lección 4.

Creer que un fake "bien escrito" hace innecesario el lado real. Qué pasa: alguien confía tanto en su fake ("lo escribí con cuidado, hace exactamente lo que el real") que salta la verificación contra SQLite. Por qué pasa: un fake correcto hoy se siente permanente y fiel. Cómo detectarlo: la divergencia del datetime del módulo 1 no venía de un fake descuidado —el fake era impecable—; venía de que SQLite serializa y el dict no. Ese abismo no lo cierra el cuidado. Cómo corregirlo: acepta que el fake y el real son tecnologías distintas y pueden divergir aunque el fake esté perfecto. La única forma de saber que coinciden es correr el contrato contra los dos. El cuidado no sustituye a la verificación.

Leer 4 deselected como un error. Qué pasa: aparece 4 passed, 4 deselected y alguien se preocupa: "¿por qué se saltaron cuatro tests?". Por qué pasa: "deselected" suena a que algo se omitió por accidente. Cómo detectarlo: si usaste -k fake, los cuatro casos [sqlite] fueron deseleccionados a propósito por el filtro; no es un fallo, es lo que pediste. Cómo corregirlo: -k selecciona por nombre; 4 deselected confirma que el filtro dejó fuera los cuatro [sqlite], como querías para mirar solo el lado del fake. Cuando quieras los ocho, quita el -k y corre la batería entera —justo lo que hace la lección 4—.

Ejercicios

Ejercicio 1 — ¿Qué probó exactamente el verde? Corriste -k fake y obtuviste 4 passed. Enuncia, con precisión, qué afirma ese resultado y qué no afirma. Usa la cláusula 1 (guardar-y-leer) como ejemplo concreto.

Ver solución

Lo que afirma: que el FakeBookingRepository cumple las cuatro cláusulas tal como tú las escribiste. Para la cláusula 1, afirma que en el fake, repo.save(booking) seguido de repo.get("bk-1") devuelve un objeto igual a booking. Es coherencia entre tu doble y tu contrato: el fake no contradice ninguna de tus suposiciones.

Lo que NO afirma: que el SqliteBookingRepository real cumpla esas cláusulas, ni —lo que es lo mismo— que el fake coincida con el real. Para la cláusula 1, el verde del fake no dice nada sobre si el real devuelve la misma reserva: el fake guarda el objeto entero y lo devuelve idéntico (datetime incluido) por su naturaleza de dict, mientras que el real serializa y podría devolver el start como str. Esa divergencia vive en el lado [sqlite], que este resultado no tocó.

La frase exacta: el verde contra el fake prueba coherencia interna (el fake concuerda con el contrato), no verdad (el fake concuerda con lo real). La verdad requiere el lado real, y es la lección 4.

Ejercicio 2 — El fake que sí falla el contrato. Un compañero escribió el fake con return self._store.get(booking_id) en get (con .get(), que devuelve None) en vez de return self._store[booking_id]. Sin correr, di qué caso [fake] se pondría rojo y con qué mensaje, y por qué esto demuestra que el verde del fake tiene valor.

Ver solución

Se pondría rojo test_get_of_a_missing_id_raises[fake]. La cláusula 2 hace with pytest.raises(KeyError): repo.get("does-not-exist"). Con .get(), el fake devuelve None para un id ausente en vez de lanzar KeyError, así que pytest.raises no ve la excepción esperada y reporta Failed: DID NOT RAISE KeyError. El resto de las cláusulas [fake] seguirían verdes.

Por qué esto demuestra que el verde del fake tiene valor: porque el contrato contra el fake caza un fake mal escrito —uno que rompe una cláusula—. El verde del fake no es inútil; certifica que el fake es coherente con el contrato, y eso descarta el fake descuidado del módulo 2. Lo que el verde del fake no puede cazar es una divergencia que el fake no exhibe por su tecnología (como la serialización del datetime), porque ahí el fake y el contrato comparten la misma suposición. En resumen: el verde del fake caza errores dentro del doble, pero no las diferencias entre el doble y lo real. Por eso es necesario, y por eso no es suficiente.

Ejercicio 3 — El examen y la clave. Traduce la analogía del autoexamen a la verificación del contrato: empareja cada elemento con su equivalente y explica el emparejamiento en una frase. (a) escribir tú mismo las preguntas y la clave; (b) sacar cien en tu autoexamen; (c) la clave oficial externa; (d) un tema que entendiste mal.

Ver solución
  • (a) Escribir tú las preguntas y la clave ↔ escribir el fake y el contrato. Ambos salen de tu misma cabeza y tus mismas suposiciones: el fake y el contrato comparten autor, así que comparten sesgos.
  • (b) Sacar cien en tu autoexamen ↔ el verde del contrato contra el fake. Los dos miden coherencia contigo mismo —tus respuestas concuerdan con tu clave, tu fake concuerda con tu contrato—, no dominio de la materia ni coincidencia con lo real.
  • (c) La clave oficial externa ↔ el SqliteBookingRepository real. Es la referencia que no escribiste para pasar tu examen: se comporta según SQLite, no según tus suposiciones, y por eso corregir contra ella (correr el contrato contra [sqlite]) sí prueba si tu doble dice la verdad.
  • (d) Un tema que entendiste mal ↔ una suposición falsa sobre el repositorio (p. ej., "get devuelve None"). Si tu comprensión es errónea, tu pregunta y tu respuesta comparten el error y la autocorrección lo bendice; igual, si tu suposición es falsa, el fake la comparte y el contrato la aprueba en verde. Solo la clave externa —o el provider real— lo delata.

La moraleja: nadie tira el autoexamen —es útil para practicar—, pero nadie se certifica solo con él. Se contrasta con la clave oficial. Lo mismo con el fake: se verifica, pero no basta; se corre también contra el real.

Resumen y siguiente paso

En esta lección corriste el contrato contra el FakeBookingRepository y viste las cuatro cláusulas [fake] en verde —4 passed, 4 deselected—, confirmando que el doble de tus unit tests cumple las promesas que el consumer necesita. Y aprendiste lo más importante de este paso: ese verde, solo, no prueba que el fake coincida con lo real. Con el autoexamen corregido con tu propia clave entendiste por qué —el fake es a la vez el sujeto y el oráculo, así que comparte tus suposiciones y no puede contradecirlas—, y viste, con la divergencia del datetime, cómo una cláusula pasa contra el fake que no serializa y podría fallar contra el real que sí. Fijaste la regla dura: un contrato verificado contra una sola implementación no es un contrato, es un test de esa implementación.

Antes de avanzar deberías poder: enunciar qué prueba y qué no prueba el verde del contrato contra el fake; explicar por qué el fake no puede cazar una divergencia que su tecnología no exhibe; y justificar por qué la verificación de un solo lado nunca es una entrega completa.

Falta el lado que convierte tu autoexamen en una certificación de verdad. En la lección 4 corres la batería completa —sin filtro— y ves [fake] y [sqlite] uno al lado del otro, ocho verdes. Ahí entenderás la transitividad que cierra el entregable 2: si el fake cumple el contrato y el real cumple el contrato, el fake y el real coinciden, y el fake ya no puede mentir sobre nada que el contrato cubra.

Recursos