Pruebas unitarias vs. pruebas de integración

Qué son las pruebas unitarias y qué verifican
Las pruebas unitarias verifican el comportamiento de la unidad más pequeña de código de forma aislada: normalmente una función, un método o una clase. Su objetivo es comprobar que esa pieza produce la salida esperada para un conjunto de entradas determinado, sin depender del sistema completo. Por ejemplo, si tienes una función que calcula el precio con impuestos, una prueba unitaria valida que, dado un importe de 100 y un impuesto del 21%, el resultado sea 121, y que casos límite como importes negativos o cero se gestionen correctamente.
Una característica esencial de estas pruebas es el aislamiento. Cuando la unidad depende de otros componentes (una base de datos, un servicio externo o el reloj del sistema), se reemplazan esas dependencias por dobles de prueba como mocks, stubs o fakes. Así se garantiza que un fallo señala un problema en la lógica probada y no en un servicio ajeno. Esto también hace que las pruebas sean deterministas: dan siempre el mismo resultado.
Las pruebas unitarias suelen escribirse siguiendo el patrón Arrange-Act-Assert: se prepara el escenario, se ejecuta la acción y se comprueba el resultado. Son rápidas de ejecutar (milisegundos), por lo que se corren cientos o miles en pocos segundos. Esa velocidad las convierte en la base de la retroalimentación continua durante el desarrollo, y encajan de forma natural con enfoques como TDD, donde la prueba se escribe antes que el código de producción.
Qué son las pruebas de integración y su alcance
Las pruebas de integración verifican que varios componentes funcionan correctamente cuando trabajan juntos. En lugar de aislar una unidad, comprueban las interacciones: cómo un módulo llama a otro, cómo la aplicación se comunica con la base de datos, cómo se serializan los datos que viajan por una API o cómo interactúan varias capas de una arquitectura. El foco está en los puntos de contacto, que suelen ser donde aparecen los errores que las pruebas unitarias no detectan.
Su alcance es variable. Una integración estrecha puede probar solo dos clases colaborando; una más amplia puede levantar una base de datos real, un servicio de mensajería o un contenedor con el sistema casi completo. Un ejemplo típico es verificar que un repositorio guarda y recupera correctamente una entidad usando una base de datos real (o una en memoria equivalente), incluyendo mapeos, transacciones y consultas. Otro ejemplo es comprobar que un endpoint HTTP responde con el código y el cuerpo correctos cuando recibe una petición válida.
Como estas pruebas usan recursos reales o casi reales, son más lentas y requieren más preparación: configurar entornos, datos de partida y limpieza tras cada ejecución. Herramientas de contenedores efímeros ayudan a levantar dependencias reproducibles. A cambio de ese coste, ofrecen confianza sobre que las piezas encajan, algo que ningún conjunto de pruebas unitarias puede garantizar por sí solo.
Diferencias clave entre pruebas unitarias e integración
La diferencia fundamental está en el alcance. Las pruebas unitarias examinan una pieza aislada; las de integración examinan la colaboración entre piezas. De ahí derivan casi todas las demás distinciones. En cuanto a dependencias, las unitarias sustituyen todo lo externo por dobles, mientras que las de integración usan implementaciones reales de al menos algunas dependencias.
La velocidad y la estabilidad también difieren. Las unitarias son rápidas y deterministas; las de integración son más lentas y, si no se gestionan bien, más propensas a resultados intermitentes por estado compartido, tiempos de espera o datos residuales. Cuando falla una prueba unitaria, el diagnóstico suele ser inmediato porque el ámbito es reducido. Cuando falla una de integración, la causa puede estar en cualquiera de los componentes implicados o en su configuración, lo que exige más investigación.
Otra diferencia está en qué defectos capturan. Las unitarias detectan errores de lógica, cálculos incorrectos y ramas no gestionadas. Las de integración detectan contratos mal entendidos, incompatibilidades de formatos, errores de configuración y problemas de persistencia. La pirámide de pruebas resume la relación recomendada: muchas pruebas unitarias en la base, un número menor de integración en el centro y aún menos pruebas de extremo a extremo en la cima. La proporción exacta depende del sistema, pero la idea de tener más pruebas rápidas que lentas es un buen punto de partida.
Ventajas y limitaciones de cada tipo de prueba
Las pruebas unitarias aportan retroalimentación casi instantánea, facilitan la refactorización con red de seguridad y actúan como documentación viva del comportamiento esperado de cada componente. Al ser baratas de ejecutar, se integran sin fricción en el flujo de trabajo local y en la integración continua. Su limitación es evidente: al probar en aislamiento, no garantizan que el sistema completo funcione. Un conjunto de unidades perfectas puede fallar en cuanto se conectan, porque los supuestos sobre las dependencias no coincidían con la realidad.
Existe además el riesgo de sobreespecificar: pruebas unitarias demasiado acopladas a la implementación interna (con muchos mocks que verifican llamadas concretas) se rompen ante cualquier refactor aunque el comportamiento externo no cambie, generando mantenimiento sin valor.
Las pruebas de integración ofrecen confianza sobre las interacciones reales y detectan clases enteras de errores invisibles para las unitarias. Su coste es el reverso de esa ventaja: son más lentas, más difíciles de configurar y su mantenimiento es más exigente. Un fallo intermitente en integración erosiona la confianza del equipo si no se investiga. La conclusión práctica es que ninguno de los dos tipos sustituye al otro: cubren riesgos distintos y se complementan.
Cómo combinar ambas en tu estrategia de pruebas
Una estrategia equilibrada empieza por identificar qué riesgo cubre cada prueba. La lógica de negocio compleja —cálculos, reglas, validaciones, transformaciones— se cubre mejor con muchas pruebas unitarias, porque necesitas probar numerosos casos y quieres que sean rápidas. Los puntos de integración —acceso a datos, llamadas entre servicios, endpoints, colas de mensajes— se cubren con pruebas de integración centradas en el contrato y el comportamiento observable.
Evita duplicar esfuerzo: no repruebes toda la lógica de negocio a través de pruebas de integración lentas si ya está cubierta por unitarias. Reserva las de integración para lo que solo puede verificarse con componentes reales colaborando. Un patrón útil es probar exhaustivamente los casos límite en el nivel unitario y validar el 'camino feliz' más algunos escenarios de error críticos en el nivel de integración.
En la práctica, conviene ejecutar las pruebas unitarias en cada guardado o commit local por su rapidez, y las de integración en la canalización de CI o en fases dedicadas. Mantén las de integración aisladas entre sí: cada una debe preparar y limpiar sus propios datos para evitar dependencias de orden. Herramientas de contenedores efímeros ayudan a que el entorno sea reproducible en cualquier máquina. Revisa periódicamente la distribución: si las pruebas tardan demasiado, quizá hay demasiadas de integración cubriendo lógica que debería estar en unitarias.
Errores comunes al aplicar pruebas unitarias e integración
El error más frecuente es confundir los niveles: escribir pruebas que se llaman unitarias pero acceden a la base de datos o a la red, con lo que se vuelven lentas e inestables sin las ventajas reales de la integración. El opuesto también ocurre: cubrir cada detalle mediante pruebas de extremo a extremo, obteniendo una suite frágil y lentísima.
Otro problema habitual es el abuso de mocks. Simular en exceso convierte las pruebas en un espejo de la implementación y no del comportamiento; el resultado son pruebas que pasan siempre pero no detectan regresiones reales. Conviene mockear las fronteras del sistema, no cada colaborador interno.
La gestión deficiente de datos y estado en integración provoca pruebas intermitentes: fallos que aparecen y desaparecen según el orden de ejecución. La solución es garantizar aislamiento y limpieza. También es común perseguir métricas de cobertura como fin en sí mismo; una cobertura alta con aserciones débiles da falsa seguridad. Es preferible medir qué comportamientos están protegidos. Por último, muchos equipos descuidan el mantenimiento: pruebas ignoradas o comentadas 'temporalmente' que nunca se recuperan. Una suite en la que no se confía deja de cumplir su función, así que tratar los fallos intermitentes como defectos prioritarios es parte esencial de la estrategia.
Ejemplo
Comparación entre pruebas unitarias y pruebas de integración
| Criterio | Pruebas unitarias | Pruebas de integración |
|---|---|---|
| Alcance | Una unidad aislada (función, método, clase) | Varios componentes colaborando |
| Dependencias | Sustituidas por mocks, stubs o fakes | Reales o casi reales |
| Velocidad | Muy rápidas (milisegundos) | Más lentas por recursos reales |
| Determinismo | Alto y estable | Menor si no se aísla el estado |
| Defectos que detectan | Errores de lógica y casos límite | Contratos, formatos, configuración, persistencia |
| Coste de mantenimiento | Bajo si no se sobreespecifica | Mayor por configuración y datos |
| Momento de ejecución | En cada commit o guardado local | En la canalización de CI |
FAQ
¿Debo escribir primero pruebas unitarias o de integración? Lo habitual es empezar por las unitarias, ya que son rápidas y guían el diseño de la lógica de cada componente. Las de integración se añaden para cubrir los puntos de contacto entre módulos y con recursos externos. Ambas se complementan, así que la decisión depende del riesgo que quieras cubrir en cada momento.
¿Una prueba que accede a la base de datos es unitaria o de integración? Es de integración. Cualquier prueba que use una dependencia real como una base de datos, la red o el sistema de archivos deja de ser unitaria, porque ya no prueba una unidad aislada. Si necesitas velocidad y aislamiento, sustituye esa dependencia por un doble de prueba.
¿Puedo prescindir de las pruebas de integración si tengo alta cobertura unitaria? No es recomendable. Una cobertura unitaria alta no garantiza que los componentes encajen entre sí, porque las pruebas unitarias sustituyen las dependencias por dobles. Los errores de contrato, configuración o persistencia solo se detectan cuando las piezas reales colaboran, y eso requiere pruebas de integración.
¿Cuántas pruebas de cada tipo conviene tener? La pirámide de pruebas sugiere muchas unitarias en la base, menos de integración en el centro y pocas de extremo a extremo en la cima. La proporción exacta depende del sistema, pero el principio de tener más pruebas rápidas que lentas ayuda a mantener una retroalimentación ágil.
Sigue leyendo
Más información