Módulo 5: Integración de verdad: componentes reales juntos
4. Qué doblar y qué mantener real
Descripción
En las lecciones anteriores tomamos una decisión una y otra vez sin explicarla: dejamos real el repositorio y doblamos el pago, el correo y el reloj. Fue el instinto correcto, pero un instinto no se puede enseñar ni defender ante un revisor. Esta lección convierte ese instinto en una regla que puedas aplicar a conciencia en cualquier integración, no solo en Reservo. Porque la pregunta "¿qué dejo real y qué doblo?" es la decisión de diseño de una prueba de integración, y equivocarse en cualquiera de las dos direcciones arruina el test: si doblas de más, no pruebas ninguna junta real (vuelves a un unit test disfrazado); si dejas real de más, heredas la lentitud, la fragilidad y los efectos de piezas que no querías tocar (te acercas a un end-to-end caro).
La regla tiene dos mitades que se complementan. Mantén real la costura que estás probando. Si el propósito del test es verificar que BookingService y el repositorio colaboran, el repositorio tiene que ser real —esa es la junta bajo prueba, y doblarla sería como el plomero con su jeringa—. Dobla lo lento, lo no determinista y lo externo. Todo colaborador que no es la costura bajo prueba y que además es caro (por lento, por frágil, por tener efectos en el mundo) se dobla, para que el test corra rápido, dé siempre el mismo resultado y no cobre tarjetas ni mande correos. En Reservo eso significa: real el repositorio (la costura), doblados el pago (externo, con efectos, cuesta dinero), el reloj (no determinista) y el correo (externo, con efectos). Esta lección te da esa regla, la justifica pieza por pieza, y te muestra cómo se ve una integración que la aplica bien.
Conexión con el módulo: esta lección es el criterio que faltaba para escribir integraciones con intención en vez de por costumbre. La lección 2 definió la integración por su costura; la 3 la hizo tangible; esta te dice qué poner alrededor de esa costura. Es la bisagra del módulo: con esta regla puedes leer cualquier test de integración y decir si está bien montado, y la lección 5 le pondrá los nombres técnicos (solitario, sociable) a los grados de "cuánto real dejas alrededor". Y prepara la 6: cuando dejes real el repositorio pero doblado el reloj, el book→cancel→get seguirá siendo determinista y cruzará la costura de verdad —justo la mezcla que hace falta para cazar el bug sin heredar la fragilidad del tiempo real—.
Analogía: el simulador de vuelo
Piensa en cómo se entrena a un piloto para aterrizar en un aeropuerto nuevo y difícil. No lo mandan a hacerlo por primera vez con pasajeros a bordo —el costo de un error es inaceptable— ni tampoco lo entrenan describiéndole el aeropuerto en un salón —eso no prueba que sepa aterrizar—. Lo ponen en un simulador de vuelo, y ahí se toma una decisión pieza por pieza sobre qué es real y qué es simulado. La cabina es real: los mismos controles, las mismas palancas, la misma disposición exacta que el avión de verdad, porque eso es lo que el piloto está practicando —la interacción entre sus manos y los mandos—. En cambio, el mundo exterior (el clima, la pista, las montañas) es simulado: proyectado en pantallas, controlado por el instructor, que puede poner niebla o viento cuando quiera, repetir el mismo escenario diez veces idéntico, y nunca hay un avión de verdad que se pueda estrellar. Real lo que se prueba (la interacción con los mandos); simulado lo caro, lo peligroso y lo impredecible (el mundo).
Una prueba de integración es un simulador. La costura que pruebas —los mandos que el piloto opera— se deja real: es lo que estás verificando, y falsearla no probaría nada. El resto del mundo —el pago que cuesta dinero, el correo que llega a bandejas reales, el reloj que avanza solo— se simula con dobles: caro, con efectos, impredecible, exactamente lo que no quieres disparar de verdad ni depender de que colabore. La maestría del diseño de integraciones es la misma que la del diseño de un simulador: saber qué parte del mundo dejar real porque es lo que pruebas, y qué parte simular porque su costo no aporta a lo que quieres verificar.
La regla de oro, en dos mitades
Escribámosla para poder aplicarla a cualquier costura, no solo a Reservo:
En una prueba de integración, mantén real la costura que estás probando y dobla todo colaborador que sea lento, no determinista o externo y que no sea esa costura.
Desglosemos las dos mitades.
Mantén real la costura bajo prueba. El propósito de la integración es verificar una junta concreta con piezas reales de los dos lados (lección 2). Esa junta no se puede doblar, por definición: doblarla sería reemplazar justo lo que quieres probar. En Reservo, si el test verifica la persistencia, el repositorio es real, sin excepción. Este es el lado no negociable de la regla.
Dobla lo lento, lo no determinista y lo externo. Todo lo demás —los colaboradores que no son la costura bajo prueba— se evalúa por su costo, y se dobla si cae en alguna de estas tres categorías:
- Lento: una llamada por red, un servicio remoto, una operación pesada. Un test de integración ya paga la costura real que prueba; no debe pagar además la lentitud de piezas que no verifica.
- No determinista: algo que da un resultado distinto cada vez —el reloj (
datetime.now()), un generador aleatorio, el orden de una respuesta externa—. Un test que depende del tiempo real es flaky: pasa hoy y falla mañana sin que tu código cambie. El reloj se dobla con unFixedClockpara que el "ahora" sea un dato del test. - Externo (con efectos en el mundo): algo que toca un sistema fuera de tu control o produce un efecto irreversible —cobrar una tarjeta, mandar un correo, escribir en un servicio de terceros—. Doblar el pago y el correo evita cobros y correos reales, y de paso te deja verificar el efecto con un spy sin dispararlo.
En Reservo, la costura bajo prueba es el repositorio, así que va real. Los tres colaboradores restantes caen justo en las tres categorías: el pago es externo y con efectos (cuesta dinero), el reloj es no determinista, el correo es externo y con efectos. Los tres se doblan. La integración resultante prueba de verdad la única junta que le importa y no hereda el costo de ninguna otra.
Ejemplo trabajado: real la costura, doblado el resto
Veamos la regla aplicada. La costura bajo prueba es el repositorio, así que es un SqliteBookingRepository real. El pago es un StubPaymentGateway (no cobra, y de paso registra el monto para verificarlo), el reloj un FixedClock (determinista), el correo un SpyEmailSender (no manda, y registra el envío). El test verifica la costura real con repo.get(...) y comprueba, con los dobles, los efectos que no quisimos disparar de verdad.
# tests/test_what_to_double.py — real la costura que pruebas, doblado el resto
import sqlite3
from datetime import datetime
from reservo.calendar import Calendar
from reservo.doubles import FixedClock, SpyEmailSender, StubPaymentGateway
from reservo.models import Member, Room
from reservo.services import BookingService
from reservo.sqlite_repo import SqliteBookingRepository
FOCUS = Room(id="focus", name="Focus", capacity=4, hourly_cents=2500)
ANA = Member(id="m-ana", name="Ana", tier="pro")
START = datetime(2026, 3, 10, 9)
END = datetime(2026, 3, 10, 12)
CLOCK = datetime(2026, 3, 1, 9)
def test_book_keeps_repo_real_doubles_the_rest():
repo = SqliteBookingRepository(sqlite3.connect(":memory:")) # REAL: la costura
payments = StubPaymentGateway(ok=True) # doblado: cuesta dinero
emails = SpyEmailSender() # doblado: externo
clock = FixedClock(CLOCK) # doblado: no determinista
service = BookingService(Calendar(), clock, payments, emails, repo)
booking = service.book(FOCUS, ANA, START, END)
# La costura real (el repo) se verifica de verdad:
assert repo.get(booking.id).status == "confirmed"
# Los dobles registran los efectos que NO queremos disparar de verdad:
assert payments.charges == [(6000, "m-ana")] # cobro (stub/spy, sin tarjeta real)
assert emails.sent == [
("m-ana", "Reserva confirmada", "Reserva de Focus confirmada.")] # correo (spy)
Léelo como un mapa de decisiones. Cada colaborador lleva un comentario con la razón de su elección: repo real porque es la costura; payments doblado porque cuesta dinero; emails doblado porque es externo; clock doblado porque es no determinista. Las aserciones también se reparten con lógica: la costura real se verifica contra la base de datos (repo.get(...).status), y los efectos que doblamos se verifican con las herramientas de sus dobles (payments.charges, emails.sent). Cada aserción mira la fuente donde su efecto deja huella.
Qué esperar. En mi máquina (Python 3.14.0, pytest 9.1.1):
python3 -m pytest tests/test_what_to_double.py -v
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0
collected 1 item
tests/test_what_to_double.py::test_book_keeps_repo_real_doubles_the_rest PASSED [100%]
============================== 1 passed in 0.01s ===============================
Verde, y —esto es lo que importa— verde por las razones correctas. El test ejerció la costura real: la reserva se escribió en SQLite y se leyó de vuelta confirmada. Y verificó los efectos sin dispararlos: el pago registró un cobro de 6000 a m-ana sin tocar una tarjeta, el correo registró un envío sin llenar una bandeja. Corrió en 0.01s, determinista, sin efectos en el mundo. Esta es la forma de una buena integración: una costura real probada de verdad, rodeada de dobles que mantienen el test rápido, repetible y sin consecuencias. Si mañana quisieras integrar otra costura —digamos la del pago—, invertirías la elección: real el pago (contra un sandbox), doblado el repositorio. La regla es la misma; lo que cambia es cuál costura estás probando.
Los dos extremos que la regla evita
Vale la pena ver qué pasa cuando se ignora cada mitad de la regla, porque los dos errores son comunes y opuestos.
Doblar de más: el unit test disfrazado. Si doblas también el repositorio —dejas todo doblado—, ya no tienes una integración: tienes un unit test. No hay ninguna junta real bajo prueba; verificas la lógica de book suponiendo a todos sus colaboradores. Está perfecto como unit test —rápido, preciso— pero no te da lo que una integración compra: la confianza de que las piezas reales colaboran. Si crees que estás integrando y en realidad doblaste la costura, tienes una falsa sensación de cobertura: piensas que probaste la junta y no la tocaste.
Dejar real de más: el end-to-end caro. Si dejas reales también el pago y el correo, tu test cobra tarjetas de verdad y manda correos de verdad. Se volvió lento (llamadas por red), frágil (falla si el servicio externo está caído), con efectos (deja cobros y correos reales que hay que limpiar) y no determinista (depende de sistemas que no controlas). Cruzaste de la integración —una costura real, el resto simulado— al end-to-end —todo real—, que es legítimo en su lugar pero mucho más caro, y que en esta guía queda fuera de frontera (es territorio de testing-backend-applications-guide). Probaste más, sí, pero pagaste un precio desproporcionado por probar juntas que no te preocupaban.
La regla de oro navega entre los dos: real la costura que importa (ni un unit test disfrazado), doblado lo caro que no es esa costura (ni un end-to-end innecesario). Ese es el punto dulce de la integración práctica.
Errores comunes
Doblar el reloj "por si acaso" pero dejar real algo que sí debía doblarse. Qué pasa: alguien dobla el reloj —bien— pero deja real el EmailSender porque "total, es solo un correo". Por qué pasa: el reloj se ve obviamente no determinista, pero el correo parece inofensivo. Cómo detectarlo: pregúntate por cada colaborador real si es lento, no determinista o externo con efectos. El correo es externo con efectos: mandarlo de verdad llena bandejas y depende de un servidor. Cómo corregirlo: aplica la regla a cada colaborador, no solo a los sospechosos obvios. Si no es la costura bajo prueba y cae en una de las tres categorías, se dobla. El correo se dobla con un spy, que además te deja verificar el envío.
Doblar la costura que dices estar probando. Qué pasa: alguien titula el test "integración del repositorio" pero le pasa un FakeBookingRepository. Por qué pasa: el fake es más cómodo y rápido, y la costumbre de doblar es fuerte. Cómo detectarlo: lee qué pieza está enchufada en la costura que el título y las aserciones dicen probar. Si es un doble, no estás integrando esa costura. Cómo corregirlo: la primera mitad de la regla es no negociable —la costura bajo prueba va real—. Si el fake es más cómodo, quizá lo que quieres es un unit test (y está bien), pero entonces no lo llames integración ni creas que probaste la junta.
Creer que "más real es siempre más seguro". Qué pasa: alguien, para "estar más seguro", deja reales el pago y el correo además del repositorio. Por qué pasa: la intuición de que más realidad es más confianza. Cómo detectarlo: si tu integración cobra tarjetas, manda correos o tarda segundos, dejaste real algo que la regla manda doblar. Cómo corregirlo: más real no es más seguro; es más caro y más frágil por costuras que no te importaban. La confianza que buscas es sobre una costura (el repositorio); las demás las cubren el unit test y el contrato, más baratos. Deja real solo lo que pruebas, y dobla el resto por costo.
Ejercicios
Ejercicio 1 — Aplica la regla, colaborador por colaborador. Quieres una integración que verifique la costura BookingService↔repositorio en el flujo de cancel. Para cada uno de los cinco colaboradores —calendar, clock, payments, emails, repo— di si lo dejas real o lo doblas, y con qué doble, justificándolo con la regla.
Ver solución
repo→ REAL (SqliteBookingRepository). Es la costura bajo prueba; la primera mitad de la regla manda dejarla real. Sin esto no hay integración del repositorio.clock→ doblado (FixedClock). No determinista: el reembolso decanceldepende del "ahora", y el reloj real haría el test flaky y cambiaría el resultado según cuándo lo corras. Congelarlo hace que cada ancla (72/36/12 h) sea un dato del test.payments→ doblado (StubPaymentGateway). Externo con efectos:cancelreembolsa, y un reembolso real movería dinero de verdad. El stub no mueve nada y, como spy, registra el reembolso para verificarlo.emails→ doblado (SpyEmailSender). Externo con efectos:cancelavisa por correo. Doblarlo evita mandar el correo y deja verificar que se envió.calendar→ real o doblado, da igual (en memoria, barato). ElCalendares una estructura en memoria, ni lento ni no determinista ni externo; no cae en ninguna categoría de la regla. Dejarlo real no cuesta nada y no es la costura bajo prueba, así que cualquiera de las dos opciones es aceptable. Lo normal es dejar elCalendar()real por simplicidad, porque doblarlo no aporta.
El patrón: la costura bajo prueba va real (repo); lo caro que no es esa costura se dobla (clock, payments, emails); lo barato y sin frontera es indiferente (calendar). Eso es la regla, aplicada.
Ejercicio 2 — Diagnostica una integración mal montada. Un compañero escribe un test que llama "integración de book" con estos colaboradores: Calendar() real, datetime.now() real como reloj, PaymentGateway(api_key=...) real, SmtpEmailSender(host=...) real, FakeBookingRepository(). Señala los dos errores de diseño según la regla y arréglalos.
Ver solución
Hay dos errores, uno por cada mitad de la regla:
Error 1: doblaron la costura que dicen probar. El test se llama "integración de book" pero usa FakeBookingRepository(), un doble, en la costura del repositorio. Si el propósito es integrar la persistencia, esa costura tiene que ser real. Tal como está, no integra nada del repositorio: es un unit test con un montón de piezas reales caras alrededor.
Error 2: dejaron reales tres colaboradores que la regla manda doblar. El datetime.now() real es no determinista (test flaky); el PaymentGateway real es externo con efectos (cobra tarjetas de verdad); el SmtpEmailSender real es externo con efectos (manda correos de verdad). Los tres deberían doblarse.
El arreglo invierte ambas decisiones: deja real el repositorio (SqliteBookingRepository, la costura bajo prueba) y dobla el pago, el correo y el reloj (StubPaymentGateway, SpyEmailSender, FixedClock). El Calendar() real puede quedarse (es barato y sin frontera). El resultado es la integración del ejemplo trabajado: real la costura, doblado lo caro, determinista y sin efectos. El test original tenía todo al revés: real lo que debía doblar, doblado lo que debía dejar real.
Ejercicio 3 — La misma regla, otra costura. Explica cómo cambiaría la elección de qué doblar y qué dejar real si el propósito del test fuera integrar la costura BookingService↔EmailSender (verificar que el servicio y un enviador de correos real colaboran), en vez de la del repositorio. ¿Qué dejarías real y qué doblarías, y por qué la regla sigue siendo la misma aunque las respuestas cambien?
Ver solución
Si la costura bajo prueba fuera BookingService↔EmailSender, la primera mitad de la regla mandaría dejar real el enviador de correos (por ejemplo, un enviador de verdad apuntando a un servidor de correo de prueba, o un http.server local que reciba la petición —justo el tipo de frontera del módulo 6—). Y doblarías todo lo que no es esa costura y es caro: el repositorio pasaría a ser un FakeBookingRepository (ya no es la costura bajo prueba, y el fake es más rápido), el pago un StubPaymentGateway, el reloj un FixedClock. La aserción verificaría que el correo real salió por la costura real (que el servidor de prueba lo recibió).
Fíjate en que las respuestas se invirtieron respecto al ejemplo del repositorio —antes el repo era real y el correo doblado; ahora el correo es real y el repo doblado— pero la regla es idéntica: mantén real la costura que pruebas, dobla lo caro que no es esa costura. Lo que cambia no es la regla, sino cuál costura declaraste probar. Esa es la potencia de la regla de oro: no es una lista de "el repo siempre real, el correo siempre doblado", sino un criterio que se reevalúa según la junta bajo prueba. (Dicho esto, integrar el correo real es una frontera de red que esta guía trata en su forma mínima con http.server en el módulo 6; la costura del repositorio, en proceso con SQLite, es la que trabajamos a fondo aquí.)
Resumen y siguiente paso
En esta lección convertiste el instinto en criterio con la regla de oro de la integración: mantén real la costura que estás probando (el repositorio, no negociable) y dobla lo lento, lo no determinista y lo externo que no es esa costura (el pago, el reloj, el correo). Con el simulador de vuelo entendiste el porqué: real lo que se practica (los mandos, la costura), simulado lo caro, peligroso e impredecible (el mundo exterior, los colaboradores costosos). Viste la regla aplicada en un test que prueba la costura real con repo.get(...) y verifica los efectos doblados con sus spies, verde y sin consecuencias; y ubicaste los dos extremos que evita —el unit test disfrazado (doblar de más) y el end-to-end caro (dejar real de más)—.
Antes de avanzar deberías poder: aplicar la regla a cada colaborador de una costura dada, justificando real o doblado; diagnosticar una integración mal montada (costura doblada, colaboradores caros reales) y arreglarla; y explicar por qué la misma regla da respuestas distintas según cuál costura declares probar.
Ya sabes qué dejar real y qué doblar. La lección 5 le pone nombre técnico a los grados de esa decisión: un test solitario dobla a todos los vecinos de la unidad; uno sociable deja reales a uno o más. Vas a ver book con el fake (solitario) y book con el repositorio real (sociable) lado a lado, y a entender por qué el sociable parcial —un vecino real, el resto doblado, justo la mezcla de esta lección— es el caballo de batalla de la integración práctica.
Recursos
- Martin Fowler — UnitTest (solitary vs sociable) — el marco que nombra "solitario" y "sociable", los grados de "cuántos vecinos reales" que la lección 5 desarrolla y que la regla de esta lección decide colaborador por colaborador.
test-doubles-and-test-data-guide— la guía hermana donde construiste elStubPaymentGateway, elSpyEmailSendery elFixedClock; útil para recordar qué verifica cada tipo de doble (el stub controla lo que entra, el spy registra lo que sale) cuando decides con qué doblar.- Documentación de pytest — Cómo escribir aserciones — la referencia para las aserciones del ejemplo, donde la costura real se verifica contra la base de datos y los efectos doblados contra sus spies, cada uno en su fuente.
sqlite3— DB-API para SQLite (documentación de Python) — la pieza que dejamos real por ser la costura bajo prueba; el recurso en proceso, sin frontera de red, que hace de "lo real" barato de integrar frente al pago o el correo, que son externos.