Módulo 5: Integración de verdad: componentes reales juntos
2. Qué es una prueba de integración de verdad
Descripción
La lección 1 te mostró una integración sin definirla con rigor: dijimos "dos piezas reales trabajando juntas" y lo dejamos ahí. Ahora hay que clavar la definición, porque "integración" es una de las palabras más maltratadas del testing. La gente llama integración a cualquier cosa: a un test que tarda un poco, a uno que usa una librería de verdad, a uno que toca el disco de refilón, a uno que prueba dos funciones en la misma línea. Con una definición así de floja no se puede decidir nada —ni qué probar, ni qué dejar real, ni qué te garantiza el verde—. Esta lección te da la definición precisa y te enseña a usarla como un bisturí: para separar lo que de verdad es una integración de lo que solo se le parece.
La definición que vamos a usar tiene dos partes que hay que sostener juntas. Primero: una prueba de integración pone a dos o más componentes reales a trabajar juntos. Segundo, y es la parte que la mayoría se salta: esos componentes reales tienen que estar a los dos lados de la costura que estás probando. No basta con que haya una pieza real en el test; esa pieza tiene que ser la costura que te interesa verificar. Un test puede usar diez piezas reales por accidente y no integrar la costura que importa, si justo esa la dobló. Y al revés: un test con una sola pieza real puede ser una integración impecable, si esa pieza es exactamente la costura bajo prueba. La costura, no el conteo de piezas reales, es lo que define la integración.
Conexión con el módulo: esta lección afina lo que la lección 1 dejó grueso, y es la base de todo lo que sigue. Una vez que sabes que la integración se define por cuál costura cruzas con piezas reales, la lección 3 arma esa costura a fondo (BookingService↔SQLite), la 4 te dice qué dejar real y qué doblar alrededor de esa costura, y la 5 te da los nombres —solitario, sociable— para los grados de "cuántos vecinos reales". Sin la definición precisa de esta lección, esas distinciones se vuelven arbitrarias; con ella, cada una cae en su lugar. Y prepara la recompensa de la lección 6: entender que una integración afirma algo —"estas piezas colaboran"— que ningún unit test puede afirmar, por más verde que esté.
Analogía: probar la tubería contra probar el grifo
Piensa en un plomero que instaló una tubería nueva que conecta el tanque de agua con el grifo de la cocina. Quiere probar que el agua llega. Hay una prueba que parece buena y no lo es: cierra la llave que une la tubería con el tanque, conecta en su lugar una jeringa de agua que él mismo aprieta, y verifica que al apretar sale agua por el grifo. Todo real —el grifo es real, la cocina es real, el agua es real—, y sin embargo no probó nada de lo que le importaba: la unión entre el tanque y la tubería, que es donde podría estar la fuga, quedó fuera, reemplazada por su jeringa. Usó muchas cosas reales, pero dobló justo la costura que quería verificar.
La prueba de verdad es la aburrida: abre la llave que conecta el tanque real con la tubería real, y mira si el agua llega al grifo. Una sola unión real —la que le importa— probada de verdad. Eso es una integración: no "cuántas cosas reales hay en la escena", sino "¿está la costura que me preocupa cruzada por piezas reales de los dos lados?". La jeringa es el doble en la costura equivocada; la llave abierta es la costura real bajo prueba. En Reservo, doblar el repositorio y usar un Calendar real es la jeringa: mucha realidad incidental, pero la costura que quieres probar —servicio↔base de datos— quedó doblada. Dejar el repositorio real es abrir la llave.
La definición, con sus dos mitades
Escribámosla de forma que puedas aplicarla:
Una prueba de integración verifica que dos o más componentes reales colaboran correctamente a través de la costura específica que estás probando, ejerciéndola en un flujo real en vez de reemplazarla con un doble.
Las dos mitades, cada una haciendo su trabajo:
"Dos o más componentes reales". Un unit test aísla una unidad y dobla a todos sus colaboradores; una integración deja reales al menos a dos piezas que se hablan. En Reservo, BookingService (la unidad) y SqliteBookingRepository (su colaborador) son las dos piezas reales de nuestra integración básica. Nota que "real" no quiere decir "toda la constelación": el pago y el correo pueden seguir doblados —lo verás en la lección 4—; lo que importa es que las dos piezas de la costura bajo prueba sean reales.
"A través de la costura específica que estás probando". Esta es la mitad que desambigua. Toda prueba de integración es sobre una costura concreta —un punto donde dos componentes se conectan—. La costura de nuestra integración es BookingService↔BookingRepository, la interfaz save/get/find_by_room. Para que el test sea una integración de esa costura, la costura tiene que estar cruzada por piezas reales: BookingService real de un lado, SqliteBookingRepository real del otro. Si dejas el repositorio real pero doblas el pago, sigues integrando la costura del repositorio —el pago no es esa costura—. Si doblas el repositorio, aunque uses un Calendar real, no integras la costura del repositorio: la reemplazaste con un doble, como la jeringa del plomero.
De aquí sale la regla operativa: para saber si un test es una integración, no cuentes las piezas reales; identifica la costura que prueba y pregunta si esa costura tiene piezas reales a los dos lados.
Ejemplo trabajado: la pieza real incidental contra la costura integrada
Veamos las dos mitades en código. Los dos tests de abajo usan un Calendar real (una pieza real, en memoria). La diferencia está en el repositorio: el primero lo dobla con un FakeBookingRepository, el segundo lo deja real con SqliteBookingRepository. Los dos pasan en verde. Pero solo el segundo es una integración de la costura del repositorio; en el primero, esa costura está doblada, y el Calendar real es realidad incidental que no cambia lo que el test prueba.
# tests/test_real_vs_incidental.py — pieza real incidental vs costura integrada
import sqlite3
from datetime import datetime
from reservo.calendar import Calendar
from reservo.doubles import (FakeBookingRepository, 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)
# NO integra la costura del repo: el repo es un DOBLE. El Calendar real es incidental.
def test_repo_seam_is_doubled_calendar_is_incidental():
repo = FakeBookingRepository() # <-- la costura que importa, DOBLADA
service = BookingService(Calendar(), FixedClock(CLOCK),
StubPaymentGateway(ok=True), SpyEmailSender(), repo)
booking = service.book(FOCUS, ANA, START, END)
assert repo.get(booking.id).status == "confirmed"
# SI integra la costura del repo: el repo es REAL.
def test_repo_seam_is_real():
repo = SqliteBookingRepository(sqlite3.connect(":memory:")) # <-- costura REAL
service = BookingService(Calendar(), FixedClock(CLOCK),
StubPaymentGateway(ok=True), SpyEmailSender(), repo)
booking = service.book(FOCUS, ANA, START, END)
assert repo.get(booking.id).status == "confirmed"
Qué esperar. En mi máquina (Python 3.14.0, pytest 9.1.1):
python3 -m pytest tests/test_real_vs_incidental.py -v
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0
collected 2 items
tests/test_real_vs_incidental.py::test_repo_seam_is_doubled_calendar_is_incidental PASSED [ 50%]
tests/test_real_vs_incidental.py::test_repo_seam_is_real PASSED [100%]
============================== 2 passed in 0.02s ===============================
Dos verdes idénticos en pantalla, y dos cosas completamente distintas por debajo. El primero usa un Calendar real, sí —pero la costura que su aserción examina, la del repositorio, está doblada con el fake—. Ese Calendar real no aporta confianza sobre la persistencia: es incidental, decorativo para el propósito del test. El segundo deja el repositorio real, así que su book→get cruza de verdad la costura BookingService↔SQLite: la reserva se escribe en una tabla y se lee de vuelta. Si algo en esa costura estuviera roto —un INSERT mal formado, una columna que no existe, una restricción del esquema—, solo el segundo test lo notaría. El primero seguiría verde, ciego, porque el fake no tiene esos problemas. Misma pantalla, garantías diferentes: uno prueba la costura del repositorio, el otro no, y la diferencia no se ve en el PASSED sino en qué pieza está enchufada en la costura.
Qué afirma una integración que un unit test no puede
Vale la pena decir explícitamente qué compras con una integración, porque es distinto de lo que compra un unit test.
Un unit test afirma: "la lógica de esta unidad es correcta, suponiendo que sus colaboradores se comporten como los dobles que le puse." Es una afirmación condicional —"suponiendo que..."—, y por eso es rápida y precisa: aisló la lógica de todo lo demás. Cuando test_book_focus_3h pasa con dobles, sabes que book cobra 6000 y confirma si el repositorio, el pago y el correo se comportan como los dobles.
Una prueba de integración afirma algo que el unit test no puede: "estas dos piezas reales, conectadas, colaboran correctamente a través de su costura." Ya no es condicional sobre un doble; es una afirmación sobre las piezas de verdad. Cuando test_repo_seam_is_real pasa, sabes que BookingService y SqliteBookingRepository, los de producción, se entienden: la reserva que uno crea el otro la guarda y la devuelve legible. Ningún unit test puede darte eso, porque un unit test, por definición, dobló la costura —cambió la pieza real por su suposición—.
Por eso las dos pruebas conviven y no compiten. El unit test te da velocidad y precisión sobre la lógica, con una suposición pendiente en cada costura doblada. La integración cobra esa suposición en una costura concreta, pagando con lentitud y setup (lección 7). La pregunta nunca es "unit o integración"; es "esta costura, ¿me basta con suponerla (unit + contrato) o necesito ejercerla de verdad (integración)?". La lección 4 te da el criterio; esta lección te da la definición para plantearte la pregunta bien.
Errores comunes
Confundir "usa una pieza real" con "es una integración". Qué pasa: alguien deja un Calendar real o un datetime real en un test que dobla el repositorio y lo etiqueta de integración. Por qué pasa: la presencia de algo real se siente como integración. Cómo detectarlo: identifica la costura que el test verifica en sus aserciones y pregunta si esa costura tiene piezas reales de los dos lados. Si la costura que te importa está doblada, el resto de realidad es incidental. Cómo corregirlo: nombra la costura bajo prueba antes de escribir el test, y asegúrate de cruzarla con la pieza real. La realidad que no toca esa costura no la hace integración.
Creer que una integración necesita todo real. Qué pasa: alguien piensa que para que valga como integración hay que dejar reales el pago, el correo, el reloj y la base de datos a la vez. Por qué pasa: "componentes reales" suena a "todos". Cómo detectarlo: si tu test cobra tarjetas o manda correos para "ser una integración de verdad", confundiste el alcance. Cómo corregirlo: la definición pide piezas reales a los dos lados de la costura bajo prueba, no en todas las costuras. Para integrar la costura del repositorio, basta con BookingService y SqliteBookingRepository reales; el pago y el correo, que no son esa costura, se doblan sin dejar de ser una integración legítima. (Es la lección 4.)
No nombrar la costura y terminar con un test que no prueba nada claro. Qué pasa: alguien mezcla piezas reales y dobles sin decidir qué costura verifica, y queda un test que no es ni unit limpio ni integración clara. Por qué pasa: se arma el test "a ojo", enchufando lo que hay a mano. Cómo detectarlo: si no puedes completar la frase "este test verifica que la costura ___ funciona con piezas reales", tu test no tiene un propósito nítido. Cómo corregirlo: empieza por la costura. Decide qué unión quieres probar, deja reales sus dos lados, dobla el resto por costo, y escribe la aserción que examina esa unión. Un test con costura nombrada prueba algo; uno sin ella solo corre.
Ejercicios
Ejercicio 1 — ¿Integra la costura del repositorio? Para cada test, di si es una integración de la costura del repositorio (piezas reales a los dos lados de BookingService↔repo) o no, y por qué: (a) book con SqliteBookingRepository real, pago y correo doblados; (b) book con FakeBookingRepository, pero con un Calendar real y un datetime.now() real; (c) SqliteBookingRepository.save seguido de get, sin BookingService; (d) book con los cuatro colaboradores doblados.
Ver solución
- (a) Sí integra la costura del repositorio.
BookingServicereal de un lado,SqliteBookingRepositoryreal del otro; la costurasave/getse cruza de verdad. Que el pago y el correo estén doblados no le quita nada: no son la costura bajo prueba. Es la integración canónica del módulo. - (b) No integra la costura del repositorio. El repositorio está doblado con el fake; la costura que verificarías con
repo.get(...)está reemplazada por el doble. ElCalendarreal y eldatetime.now()real son realidad incidental —además, eldatetime.now()real vuelve el test no determinista, un mal aparte—. No prueba la costura servicio↔base de datos. - (c) Sí, es una integración estrecha de la costura repositorio↔base de datos. No interviene
BookingService, pero el repositorio real se ejerce contra su recurso real (SQLite). Es la costura más estrecha: una pieza real contra su base de datos. La distinción estrecha/amplia se ve más en la lección 5; lo que no es, es un unit test con dobles. - (d) No, es un unit test solitario. Todos los colaboradores doblados; ninguna costura cruzada con piezas reales. Es el extremo aislado: prueba la lógica de orquestación de
booksuponiendo a todos sus vecinos. Útil, rápido, pero no integra nada.
La regla que aplicaste: mira la costura que el test verifica, no el total de piezas reales en escena. (a) y (c) cruzan una costura real; (b) y (d) no.
Ejercicio 2 — Traduce lo que afirma cada verde. Tienes dos tests en verde: test_book_focus_3h (todo doblado, un unit test) y test_repo_seam_is_real (repositorio real, una integración). Escribe, para cada uno, la afirmación exacta que su verde garantiza, cuidando la parte condicional.
Ver solución
test_book_focus_3h(unit, todo doblado): su verde garantiza "la lógica debookes correcta —cobra6000, guarda una reserva confirmada, manda un correo— suponiendo que el repositorio, el pago y el correo se comporten como los dobles que le puse." Es una afirmación condicional sobre la lógica de orquestación, con una suposición pendiente en cada costura doblada.test_repo_seam_is_real(integración, repositorio real): su verde garantiza "BookingServiceySqliteBookingRepository, las piezas reales, colaboran correctamente a través de su costura: una reserva creada por el servicio se escribe en la base de datos de verdad y se lee de vuelta con un estado confirmado." Ya no hay "suponiendo que el repositorio..."; el repositorio es el real, así que su comportamiento se ejerció, no se supuso.
La diferencia está justo en la cláusula condicional. El unit test la tiene ("suponiendo que el doble..."); la integración la eliminó para la costura del repositorio (usó la pieza real). Por eso la integración cierra una brecha que el unit no puede: convierte una suposición en un hecho verificado, para esa costura.
Ejercicio 3 — Diseña la integración de una costura distinta. Hasta ahora integramos la costura BookingService↔repositorio. Imagina que quisieras integrar, en cambio, la costura BookingService↔PaymentGateway con un pago real (por ejemplo, un pago de prueba contra un servicio sandbox). Describe qué piezas dejarías reales y cuáles doblarías, y explica por qué esta integración es más cara y por qué esta guía no la hace.
Ver solución
Para integrar la costura del pago, dejarías reales BookingService y el PaymentGateway de verdad (contra un entorno sandbox de la pasarela), y doblarías lo que no es esa costura: el repositorio (un fake basta), el correo (un spy), el reloj (un FixedClock). La costura bajo prueba sería servicio↔pasarela de pago, y la aserción verificaría que un cobro real se registra correctamente en el sandbox.
Es más cara por tres razones. Primero, es externa y por red: depende de un servicio de terceros que puede estar caído, lento o cambiar sin avisar, así que el test es frágil por causas ajenas a tu código. Segundo, tiene efectos y estado en otro sistema: aunque sea sandbox, dejas datos en la pasarela que hay que limpiar, y un cobro mal hecho puede tener consecuencias. Tercero, es lenta: una llamada por red tarda órdenes de magnitud más que una operación en proceso.
Esta guía no hace esa integración por su frontera: trabajamos con recursos en proceso (SQLite, y en el módulo 6 un http.server mínimo de la stdlib), sin dependencias externas ni servicios de terceros. Integrar contra una pasarela de pago real, un servicio SaaS o una base de datos remota es territorio de pruebas end-to-end y de la guía testing-backend-applications-guide. Aquí el criterio de la lección 4 nos dirá que el pago es justo lo que conviene doblar —externo, con efectos, lento—, y que la costura que sí vale integrar en proceso es la del repositorio.
Resumen y siguiente paso
En esta lección clavaste la definición que la lección 1 dejó grande: una prueba de integración verifica que dos o más componentes reales colaboran a través de la costura específica que estás probando, ejerciéndola en vez de doblarla. Con el plomero y su jeringa entendiste la mitad que casi todos se saltan: no cuenta cuántas piezas reales hay en escena, sino si la costura que te importa está cruzada por piezas reales de los dos lados. Viste, con dos verdes idénticos en pantalla, cómo un Calendar real puede ser realidad incidental mientras la costura del repositorio queda doblada, y cómo dejar el repositorio real convierte el mismo book→get en una integración de verdad. Y distinguiste lo que afirma cada verde: el unit test, condicional sobre sus dobles; la integración, un hecho sobre las piezas reales.
Antes de avanzar deberías poder: aplicar la definición identificando la costura bajo prueba y verificando que tiene piezas reales a los dos lados; distinguir una pieza real incidental de la costura integrada; y traducir el verde de un unit test (condicional) y el de una integración (sobre lo real) sin confundirlos.
Con la definición en la mano, toca armar la integración a fondo. En la lección 3 nos metemos en la costura BookingService↔SQLite de verdad: vas a ver la fila que book escribe en la tabla consultándola con SQL crudo, y comprobar que la reserva sobrevive a cerrar y reabrir el archivo en disco —la prueba más contundente de que hay una base de datos real, y no un doble disfrazado, al otro lado de la costura—.
Recursos
- Martin Fowler — IntegrationTest — la fuente que discute por qué "integración" significa cosas distintas para distintas personas y por qué conviene precisar la costura y el alcance; el marco de la definición de esta lección.
- Martin Fowler — UnitTest (solitary vs sociable) — donde se distingue el test aislado del que deja vecinos reales; contexto para lo que la lección 5 nombrará y para entender qué afirma cada tipo de verde.
- Documentación de pytest — Cómo invocar pytest (
-v) — la referencia para correr los tests con-vy leer elPASSED/FAILEDpor test, como en el ejemplo trabajado donde dos verdes esconden dos garantías distintas. sqlite3— DB-API para SQLite (documentación de Python) — la referencia del recurso real que hace de "el otro lado de la costura" cuando el repositorio es elSqliteBookingRepository, la pieza que la definición exige que sea real para que el test integre esa costura.