Módulo 1: De la unidad a la integración: por qué
6. Tipos de integración
Descripción
Hasta ahora hemos usado "test de integración" como si fuera una sola cosa: dos piezas reales juntas cruzando una costura. En la práctica hay varias clases, y confundirlas es fuente de discusiones interminables —dos personas dicen "test de integración" y quieren decir cosas distintas—. Esta lección te da los ejes para nombrar con precisión qué estás probando: cuántas piezas reales metes, hasta dónde llega el alcance, y en qué orden ensamblas. No es taxonomía por gusto: cada eje corresponde a una decisión concreta que vas a tomar al escribir un test, y tener el nombre te deja tomarla a conciencia.
Vamos a ver tres ejes. El primero, solitario contra sociable, distingue si dejas real solo la unidad bajo prueba (doblando a sus vecinos) o si dejas reales también a los vecinos. El segundo, estrecho contra amplio, distingue si el test toca una sola costura (el repositorio contra su base de datos) o un recorrido que cruza varias (un book completo con repositorio real). El tercero, incremental contra big-bang, distingue si integras las piezas de a poco, junta por junta, o todas de golpe. Cada eje aplicado a Reservo, para que la próxima vez que escribas un test sepas exactamente en qué punto de cada eje estás parado y qué dejas fuera.
Conexión con el módulo: esta lección afina el vocabulario que la lección 2 dejó grueso. La 2 te dio "unitario contra integración"; aquí ves que dentro de "integración" hay grados, y que la elección entre ellos es la que puebla el medio de la pirámide (lección 3). Los tipos que nombres aquí son los que la lección 7 sopesará por costo, y los que el módulo 5 en adelante escribirás a fondo. Saber que existe un abanico —y no un único "test de integración"— es lo que te deja elegir el punto justo de real que necesitas: ni tan poco que no pruebes la junta, ni tanto que heredes la lentitud y la fragilidad de un e2e.
Analogía: probar una cocina de restaurante
Piensa en un restaurante que estrena cocina y quiere asegurarse de que funciona antes de abrir. Hay muchas maneras de "probar la cocina junta", y cada una responde algo distinto. Puede probar una estación con sus vecinas de verdad: el cocinero de la parrilla trabajando con el ayudante real que le pasa los platos y el pase real que recoge —eso es sociable, la parrilla rodeada de sus colaboradores reales—; o probar la parrilla sola, con un ayudante figurante que solo simula pasarle los platos —eso es solitario, la estación aislada—. Puede probar una sola conexión: ¿el mesero canta la comanda y el cocinero la recibe bien? —eso es estrecho, una junta—; o probar un pedido completo de punta a punta: entra la comanda, se cocina, se emplata, sale al comedor —eso es amplio, varias juntas en cadena—. Y puede ensamblar la cocina de a poco —primero parrilla+pase, luego se suma la freidora, luego el postre—, atrapando el problema en cuanto una pieza nueva entra —eso es incremental—; o encender todo a la vez la noche de apertura y ver qué se rompe —eso es big-bang, y cuando algo falla, buena suerte averiguando cuál de las diez estaciones fue—.
Reservo es esa cocina. La "parrilla con vecinas reales" es BookingService con el repositorio real; la "parrilla sola" es BookingService con todo doblado; la "sola conexión" es el repositorio contra su base de datos; el "pedido completo" es un book que cruza pago, persistencia y correo. Y ninguna de estas pruebas es la prueba de integración: son distintas preguntas sobre las juntas, cada una con su nombre. Un buen equipo, como un buen chef, sabe cuál necesita en cada momento.
Eje 1: solitario contra sociable
Este eje es sobre cuántos vecinos de la unidad dejas reales.
- Test solitario (solitary): la unidad bajo prueba es real; todos sus colaboradores están doblados. Es, en rigor, el unit test aislado de siempre —
bookcon fake, stub y spy—. Lo mencionamos aquí porque es el extremo del eje: cero vecinos reales. - Test sociable (sociable): la unidad es real y deja reales a uno o más de sus colaboradores, para probar cómo se comportan juntos.
BookingServicecon elSqliteBookingRepositoryreal (aunque el pago y el correo sigan doblados) es sociable: la unidad "socializa" con un vecino de verdad.
La palabra "sociable" es literal: mide si la unidad se lleva bien con sus vecinos reales. En Reservo casi nunca quieres un test totalmente sociable —dejar reales el pago y el correo significa cobrar tarjetas y mandar correos—; lo normal es un test parcialmente sociable: real justo el vecino cuya junta te importa (el repositorio), doblados los demás para no heredar su costo y su fragilidad. Esa mezcla —una pieza real, el resto doblado— es el caballo de batalla de la integración práctica, y la verás una y otra vez.
Eje 2: estrecho contra amplio
Este eje es sobre cuántas costuras cruza el test.
- Integración estrecha (narrow): el test toca una sola costura, con las mínimas piezas reales para ejercerla.
SqliteBookingRepository.getde un id ausente lanzando contra la base de datos real es lo más estrecho posible: una pieza (el repositorio), una costura (repositorio ↔ base de datos), una pregunta (¿lanza cuando no existe?). Rápido, preciso, fácil de ubicar cuando falla. - Integración amplia (broad): el test recorre varias costuras en cadena. Un
bookcompleto con repositorio real cruza la costura del pago (doblada), la de la persistencia (real) y la del correo (doblada) en un solo flujo. Prueba más juntas de una vez, a cambio de más setup y de un fallo más ambiguo (¿qué costura de la cadena se rompió?).
El eje estrecho-amplio es un dial de compromiso. Cuanto más estrecho, más se parece a un unit test en sus virtudes (rápido, preciso) pero probando una junta real. Cuanto más amplio, más se parece a un e2e (realista, pero lento y ambiguo). La integración estrecha es la que más rinde por su costo, y por eso, cuando puedas, prefiere verificar una costura de forma estrecha antes que envolverla en un flujo amplio: si el datetime diverge, un test estrecho del ida-y-vuelta del repositorio te lo dice más rápido y más claro que un book completo.
Eje 3: incremental contra big-bang
Este eje es sobre el orden en que ensamblas las piezas reales.
- Integración incremental: integras de a poco, una junta a la vez. Primero verificas el repositorio contra su base de datos (estrecho); cuando esa junta está sólida, sumas
BookingServiceencima (sociable); recién entonces, si hiciera falta, un flujo más amplio. Cada vez que añades una pieza real, si algo se rompe, sabes que fue la que acabas de sumar. El fallo viene con su culpable señalado. - Integración big-bang: conectas todas las piezas reales de golpe y pruebas el conjunto. Es tentador —"probemos todo junto de una vez"—, y es donde nace el peor dolor de cabeza de depuración: cuando el conjunto falla, el bug puede estar en cualquiera de las piezas o en cualquiera de las juntas, y no tienes un orden que te diga por dónde empezar.
La recomendación práctica es casi siempre incremental, y por la misma razón que la pirámide: el fallo precoz y localizado vale oro. Construye tus tests de integración de Reservo de abajo hacia arriba —la costura del repositorio primero, el servicio sociable después— para que cada rojo apunte a la pieza recién integrada, no a una nube de sospechosos.
Ejemplo trabajado: estrecho y sociable, lado a lado
Veamos dos de estos tipos en código, sobre Reservo. El primero es una integración estrecha: una sola pieza real (el repositorio) contra su recurso real (la base de datos), verificando una sola cosa —que get de un id ausente lanza—. El segundo es sociable: BookingService real conversando con el repositorio real, con el pago y el correo doblados para no heredar su costo.
# tests/test_integration_types.py — estrecha vs sociable
import sqlite3
from datetime import datetime
import pytest
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)
# ESTRECHA — una sola pieza real contra su recurso real
def test_narrow_get_missing_id_raises_against_real_db():
repo = SqliteBookingRepository(sqlite3.connect(":memory:"))
with pytest.raises(KeyError):
repo.get("does-not-exist")
# SOCIABLE — BookingService real + repo real; pago y correo doblados
def test_sociable_book_persists_through_the_service():
repo = SqliteBookingRepository(sqlite3.connect(":memory:"))
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_integration_types.py -v
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0
collected 2 items
tests/test_integration_types.py::test_narrow_get_missing_id_raises_against_real_db PASSED [ 50%]
tests/test_integration_types.py::test_sociable_book_persists_through_the_service PASSED [100%]
============================== 2 passed in 0.01s ===============================
Dos verdes, dos tipos distintos de integración. Compara sus formas. El estrecho es minúsculo: crea el repositorio real, pide un id inexistente, verifica que lanza. Una pieza, una costura, una pregunta —si falla, sabes exactísimamente qué—. El sociable arma BookingService con cuatro colaboradores, tres de ellos doblados y uno real (el repositorio), corre un book completo y luego verifica que la reserva quedó confirmada en la base de datos real. Cruza más terreno —la lógica de orquestación y la persistencia real— a cambio de un setup mayor. Fíjate en la decisión de diseño del sociable: deja real solo al repositorio (la junta que importa) y dobla el pago y el correo (que costarían dinero y correos reales). Ese es el test parcialmente sociable del que hablábamos: real donde el riesgo vive, doblado donde el costo duele.
Otro corte útil: integración de componentes contra de subsistema
Además de los tres ejes, encontrarás dos etiquetas de alcance que conviene reconocer:
- Integración de componentes: verifica que dos componentes concretos encajan —
BookingServiceySqliteBookingRepository—. Es lo que hemos hecho toda la lección. - Integración de subsistema: verifica que un grupo mayor de componentes, un "subsistema" completo, funciona ensamblado —por ejemplo, todo el flujo de reservas de Reservo con su persistencia real, pero todavía sin la interfaz web ni la pasarela de pago de verdad—.
La frontera de esta guía vive justo aquí: llegamos hasta la integración de componentes y de subsistemas en proceso (con SQLite y, en el módulo 6, un http.server mínimo de la stdlib). El subsistema completo con un framework web de verdad —la app entera respondiendo peticiones HTTP reales— ya es integración de más alto nivel y pertenece a testing-backend-applications-guide. Nombrar los niveles te ayuda a ver dónde termina nuestro terreno y empieza el de la guía hermana.
Errores comunes
Decir "test de integración" sin especificar cuál. Qué pasa: dos personas discuten sobre "los tests de integración" y no se entienden porque una piensa en el ida-y-vuelta estrecho del repositorio y la otra en un flujo amplio de subsistema. Por qué pasa: el término, a secas, es ambiguo. Cómo detectarlo: si una conversación sobre integración da vueltas sin acuerdo, probablemente cada quien está en un punto distinto de los ejes. Cómo corregirlo: aterriza el término con los ejes —"un test sociable y estrecho del repositorio", "un test amplio de book"—. La precisión disuelve el desacuerdo, porque casi siempre las dos personas tienen razón sobre cosas distintas.
Elegir siempre lo más amplio "para probar más". Qué pasa: alguien envuelve cada verificación en un book completo con repositorio real, creyendo que cubre más. Por qué pasa: lo amplio se siente más realista y por tanto más valioso. Cómo detectarlo: si verificas la divergencia del datetime a través de un book entero cuando un ida-y-vuelta estrecho del repositorio bastaba, estás pagando setup y ambigüedad de más. Cómo corregirlo: prefiere el test más estrecho que pueda responder tu pregunta. Lo amplio se reserva para cuando la pregunta es, precisamente, sobre la cadena completa; para una sola junta, lo estrecho gana en velocidad y en claridad de fallo.
Integrar big-bang y depurar a ciegas. Qué pasa: alguien conecta todas las piezas reales de una vez, el conjunto falla, y pasa horas sin saber qué pieza culpar. Por qué pasa: "probar todo junto" parece el atajo. Cómo detectarlo: si tus fallos de integración nunca señalan una pieza concreta y siempre exigen una investigación, estás integrando big-bang. Cómo corregirlo: ensambla incremental —primero la costura del repositorio, luego el servicio sociable encima—, de modo que cada pieza nueva que sumas sea la sospechosa obvia cuando algo se rompe. El orden de ensamblaje es una herramienta de depuración, no un detalle.
Ejercicios
Ejercicio 1 — Ubica cada test en los tres ejes. Para cada uno, di dónde cae en solitario/sociable, estrecho/amplio e incremental/big-bang (cuando aplique): (a) SqliteBookingRepository.save seguido de find_by_room verificando que la fila quedó; (b) book con repositorio real, pago y correo doblados, verificando persistencia y correo; (c) book con los cuatro colaboradores reales (pago y correo de verdad incluidos).
Ver solución
- (a)
save+find_by_roomdel repo real — solitario en cuanto a vecinos (no hay otra unidad, solo el repositorio), estrecho (una costura: repositorio ↔ base de datos), y es la pieza base de una integración incremental. Es el test de integración más económico y preciso: una pieza, una junta. - (b)
bookcon repo real, pago y correo doblados — parcialmente sociable (una unidad real,BookingService, con un vecino real, el repositorio, y dos doblados), amplio (cruza pago, persistencia y correo en un flujo), y el segundo paso de una integración incremental (sumasBookingServiceencima de la costura del repositorio ya verificada en (a)). El caballo de batalla práctico. - (c)
bookcon los cuatro reales — totalmente sociable y muy amplio, tendiendo a big-bang. Es un e2e disfrazado: cobra tarjetas y manda correos de verdad. Fuera de esta guía —y en general, algo que casi nunca quieres, por costo, fragilidad y ambigüedad de fallo—.
El patrón: a medida que dejas más vecinos reales (más sociable) y cruzas más costuras (más amplio), ganas realismo pero pierdes velocidad, precisión y control del fallo. El punto dulce de Reservo es (b) reducido a lo esencial —o directamente (a)— según qué junta quieras verificar.
Ejercicio 2 — Diseña la integración incremental de Reservo. Quieres cubrir con integración la costura del repositorio y su uso desde book. Escribe el orden incremental de tests que montarías, y di qué te dice cada rojo posible.
Ver solución
El orden incremental, de la junta más pequeña hacia arriba:
SqliteBookingRepositorycontra su base de datos, estrecho. Tests:save+getconserva los campos;getde id ausente lanza;savedos veces del mismo id actualiza (no duplica);find_by_roomfiltra bien. Si algo aquí se pone rojo, el bug está en el repositorio o en su SQL —una sola pieza, fácil de ubicar—.BookingService+ repositorio real, sociable (pago y correo doblados). Test: unbookcompleto persiste una reserva confirmada legible en la base de datos real. Como la capa (1) ya está verde, si (2) se pone rojo el sospechoso es la junta nueva: cómoBookingServiceusa el repositorio (el orden de las llamadas, los datos que le pasa), no el repositorio en sí.
La ganancia de este orden: cada rojo llega con su culpable acotado. Un rojo en (1) es "el repositorio"; un rojo en (2), con (1) en verde, es "cómo el servicio usa el repositorio". Si hubieras integrado big-bang —book con repo real desde el principio, sin la capa (1)—, un rojo te dejaría dudando entre el SQL del repositorio y la lógica del servicio, y tendrías que desenredarlo a mano.
Ejercicio 3 — Estrecho o amplio para este bug. Sospechas que SqliteBookingRepository guarda mal el status (lo del ejercicio 2 de la lección 2). Tienes dos opciones para cazarlo: (a) un book amplio con repositorio real que verifique repo.get(id).status == "confirmed"; (b) un test estrecho que haga save de una reserva con status="confirmed" directamente al repositorio y luego get y verifique el status. ¿Cuál prefieres y por qué?
Ver solución
Prefiero (b), el estrecho, y por dos razones.
Primero, precisión al fallar. El bug que sospechas vive en el SQL de SqliteBookingRepository.save/get, una sola costura. El test estrecho ejercita exactamente esa costura y nada más: si falla, el culpable es inequívoco. El test amplio (a) mete de por medio toda la lógica de book —validación, cálculo de precio, la orquestación—, así que un rojo podría deberse a otra cosa, y tendrías que descartar al servicio antes de mirar el repositorio.
Segundo, velocidad y simplicidad. El estrecho no necesita armar BookingService con sus cuatro colaboradores, ni un Calendar, ni un FixedClock; solo el repositorio y una reserva. Menos andamiaje, menos que pueda salir mal por razones ajenas al bug, y corre en un parpadeo.
La regla general que estás aplicando: para verificar una junta concreta, usa el test más estrecho que la ejercite. Lo amplio se reserva para cuando la pregunta es sobre la cadena completa (¿book de punta a punta persiste bien?), no para cazar un bug que ya sabes que vive en una sola costura. El estrecho es a la integración lo que el unit test es a la lógica: la herramienta precisa.
Resumen y siguiente paso
En esta lección cambiaste "test de integración" —un término grueso— por un vocabulario con filo. Aprendiste tres ejes: solitario contra sociable (cuántos vecinos reales dejas), estrecho contra amplio (cuántas costuras cruzas) e incremental contra big-bang (en qué orden ensamblas). Viste, con el test estrecho del repositorio y el sociable de book, dos puntos concretos de ese espacio, y por qué el test parcialmente sociable —una pieza real, el resto doblado— y la integración estrecha e incremental son los caballos de batalla: máximo valor de junta por el mínimo de costo y ambigüedad. Y ubicaste la frontera de la guía: llegamos a la integración de componentes y de subsistema en proceso; el subsistema con framework web es de la guía hermana.
Antes de avanzar deberías poder: ubicar cualquier test de integración en los tres ejes; explicar por qué lo estrecho e incremental gana en precisión de fallo; y elegir el punto justo de "real" para una pregunta dada, sin caer en el big-bang totalmente sociable.
Cada uno de estos tipos tiene un costo distinto, y hasta ahora lo hemos nombrado sin medirlo. La lección 7 pone números sobre la mesa: cuánto más lento es de verdad un test de integración que un unit test —lo mediremos con el FakeBookingRepository contra el SqliteBookingRepository en disco— y cómo decidir, con ese costo en la mano, cuándo la integración vale la pena y cuándo un contrato basta.
Recursos
- Martin Fowler — IntegrationTest (narrow vs broad) — la fuente de la distinción estrecho/amplio y del matiz de por qué "integración" significa cosas distintas para distintas personas; el marco del eje 2.
- Martin Fowler — UnitTest (solitary vs sociable) — donde se acuñan "solitario" y "sociable" y se discute la escuela que prefiere cada uno; el marco del eje 1.
- Documentación de pytest —
pytest.raises— la herramienta con la que el test estrecho verifica quegetde un id ausente lanza contra la base de datos real, como en el ejemplo trabajado. testing-backend-applications-guide— la guía hermana que retoma donde terminamos: la integración de subsistema con un framework web y HTTP de punta a punta, el nivel de alcance que aquí dejamos marcado en la frontera.