6. Interfaces, contratos y responsabilidades entre componentes

6.1 Introducción

Las pruebas de integración se concentran en los puntos donde los componentes se conectan. Para probar bien esos puntos, necesitamos entender tres ideas: interfaces, contratos y responsabilidades.

La interfaz define cómo se comunican las partes. El contrato define qué acuerdos deben respetar. La responsabilidad define qué debe hacer cada componente y qué no debería hacer.

Cuando estas tres ideas están claras, las pruebas de integración son más precisas. Cuando están confusas, las pruebas tienden a ser frágiles, incompletas o difíciles de diagnosticar.

6.2 Qué es una interfaz

Una interfaz es el punto de contacto entre dos componentes. Es el medio por el cual un componente solicita algo, entrega información o recibe una respuesta.

Una interfaz puede tomar distintas formas:

  • Un método público de una clase.
  • Una función exportada por un módulo.
  • Un endpoint HTTP.
  • Una consulta a una base de datos.
  • Un mensaje publicado en una cola.
  • Un archivo generado por un proceso.
  • Una tabla compartida por varios módulos.

En pruebas de integración, no solo importa que la interfaz exista. También importa que se use de acuerdo con lo esperado.

6.3 Qué es un contrato

Un contrato es el conjunto de reglas que una interfaz debe cumplir para que los componentes puedan colaborar. Puede estar documentado formalmente o estar implícito en el código y en los casos de uso.

Un contrato puede especificar:

  • Qué datos son obligatorios.
  • Qué datos son opcionales.
  • Qué tipos de datos se aceptan.
  • Qué formato deben tener fechas, importes, identificadores o códigos.
  • Qué valores son válidos.
  • Qué respuesta se espera si la operación funciona.
  • Qué errores pueden ocurrir y cómo se representan.
La interfaz es el punto de contacto. El contrato es el acuerdo que dice cómo debe usarse ese punto de contacto.

6.4 Qué es una responsabilidad

La responsabilidad de un componente es aquello que le corresponde hacer dentro del sistema. Definir responsabilidades evita duplicaciones, confusiones y acoplamientos innecesarios.

Por ejemplo, en una aplicación con capas:

  • El controlador recibe la solicitud y prepara la respuesta.
  • El servicio aplica reglas de negocio.
  • El repositorio se encarga del acceso a datos.
  • El cliente de una API externa encapsula la comunicación con ese proveedor.

Cuando un componente asume responsabilidades que no le corresponden, las integraciones se vuelven más difíciles de probar y mantener.

6.5 Relación entre interfaz, contrato y responsabilidad

Estas tres ideas están conectadas. Una responsabilidad suele exponerse mediante una interfaz, y esa interfaz necesita un contrato para ser usada correctamente.

Por ejemplo, un servicio de usuarios puede tener la responsabilidad de registrar nuevos usuarios. Su interfaz puede ser una operación llamada crearUsuario. Su contrato puede indicar que necesita nombre, correo y contraseña, y que debe rechazar correos duplicados.

Una prueba de integración puede verificar que otro componente use esa interfaz respetando el contrato y que el servicio cumpla su responsabilidad.

6.6 Productores y consumidores

En muchas integraciones podemos identificar un productor y un consumidor. El productor entrega datos o comportamiento; el consumidor los usa.

Situación Productor Consumidor
Un servicio consulta una base de datos. Base de datos. Servicio.
Un controlador llama a un servicio. Servicio. Controlador.
Una API devuelve información de clientes. API. Cliente de la API.
Un proceso publica un evento. Proceso publicador. Proceso consumidor.

Las fallas aparecen cuando el productor entrega algo distinto de lo esperado o cuando el consumidor interpreta mal lo recibido.

6.7 Contratos de entrada

Un contrato de entrada define qué debe recibir un componente para poder trabajar correctamente. Si la entrada no cumple el contrato, el componente debería rechazarla o manejarla de forma controlada.

Al probar integraciones, conviene revisar:

  • Campos obligatorios presentes.
  • Tipos de datos correctos.
  • Formatos válidos.
  • Valores dentro de rangos permitidos.
  • Identificadores existentes.
  • Permisos o credenciales necesarias.

Una integración robusta no debe depender de entradas perfectas. También debe comportarse bien ante entradas inválidas o incompletas.

6.8 Contratos de salida

Un contrato de salida define qué devuelve o produce un componente después de ejecutar una operación. Puede ser una respuesta, un registro guardado, un archivo, un mensaje o un cambio de estado.

La prueba de integración puede verificar que la salida incluya:

  • Datos esperados.
  • Estructura correcta.
  • Estados válidos.
  • Mensajes de error comprensibles.
  • Códigos de respuesta adecuados.
  • Efectos secundarios esperados, como guardar datos o publicar eventos.

El contrato de salida es tan importante como el de entrada, porque otros componentes suelen depender de esa respuesta para continuar el flujo.

6.9 Responsabilidades claras reducen errores

Cuando cada componente tiene una responsabilidad clara, las integraciones son más simples de probar. Sabemos qué debe preparar un componente, qué debe delegar y qué resultado debe esperar.

En cambio, si las responsabilidades se mezclan, aparecen problemas como:

  • Reglas de negocio duplicadas en varios lugares.
  • Validaciones inconsistentes entre componentes.
  • Pruebas que necesitan preparar demasiadas cosas para un caso simple.
  • Dificultad para saber qué componente falló.
  • Cambios pequeños que rompen muchas pruebas.

Las pruebas de integración pueden revelar estos problemas, porque muestran cómo se comporta el sistema cuando las responsabilidades se combinan.

6.10 Ejemplo: registro de usuario

Supongamos una funcionalidad para registrar usuarios. Podemos dividir responsabilidades así:

Componente Responsabilidad Contrato relevante
Controlador Recibir la solicitud y devolver una respuesta. Debe recibir nombre, correo y contraseña.
Servicio de usuarios Aplicar reglas de registro. Debe rechazar correos duplicados y contraseñas inválidas.
Repositorio Guardar y consultar usuarios. Debe persistir los campos requeridos sin perder información.
Servicio de correo Enviar mensaje de bienvenida. Debe recibir correo, asunto y contenido.

Una prueba de integración puede verificar que estos componentes colaboren correctamente cuando se registra un usuario válido y también cuando se intenta registrar un correo duplicado.

6.11 Cambios de contrato

Los contratos cambian con el tiempo. Se agregan campos, se modifican reglas, se eliminan respuestas antiguas o se ajustan formatos. Cada cambio puede afectar a los consumidores.

Un cambio de contrato puede romper una integración aunque el componente modificado siga pasando sus pruebas internas. Por ejemplo, un servicio puede empezar a devolver un nuevo estado llamado pendiente_revision, pero el consumidor solo conoce activo e inactivo.

Las pruebas de integración ayudan a detectar estos impactos. Cuando un contrato cambia, conviene revisar qué pruebas deben actualizarse y qué consumidores pueden verse afectados.

6.12 Contratos implícitos

No todos los contratos están escritos. A veces el acuerdo vive solo en el código, en comentarios, en ejemplos o en conocimiento del equipo. Estos contratos implícitos son riesgosos porque pueden interpretarse de distintas formas.

Señales de contratos implícitos:

  • Un consumidor depende de un campo que no está documentado.
  • Un valor especial, como 0 o null, tiene un significado no explicado.
  • El orden de los elementos en una lista importa, pero nadie lo especificó.
  • Una operación debe llamarse después de otra, pero no hay una regla visible.

Una prueba de integración puede convertir parte de ese conocimiento en una verificación concreta y repetible.

6.13 Errores que una prueba debe revelar

Al probar interfaces y contratos, buscamos detectar problemas como:

  • Campos faltantes o con nombres incorrectos.
  • Tipos de datos incompatibles.
  • Valores fuera del rango permitido.
  • Errores no documentados.
  • Respuestas ambiguas.
  • Estados que el consumidor no sabe manejar.
  • Responsabilidades mezcladas entre componentes.

Estos errores no siempre producen una falla inmediata. A veces generan datos incorrectos o comportamientos inconsistentes que se descubren más tarde.

6.14 Cómo expresar el contrato en una prueba

Una prueba de integración debe dejar claro qué contrato está verificando. Para eso conviene que el caso de prueba indique:

  • Qué componentes participan.
  • Qué entrada se envía.
  • Qué respuesta o efecto se espera.
  • Qué estado final debe quedar.
  • Qué comportamiento se espera ante errores.

Por ejemplo, en lugar de escribir una prueba llamada “probar usuario”, es más claro usar un objetivo como “registrar usuario válido debe guardar el usuario y enviar confirmación”.

6.15 Buenas prácticas

Al trabajar con interfaces, contratos y responsabilidades, conviene aplicar algunas buenas prácticas:

  • Nombrar claramente las operaciones y campos.
  • Documentar los datos obligatorios y opcionales.
  • Evitar que varios componentes apliquen la misma regla de manera distinta.
  • Probar entradas válidas, inválidas y límites relevantes.
  • Verificar tanto la respuesta como el estado final.
  • Actualizar pruebas cuando cambie un contrato.
  • Priorizar contratos usados por funcionalidades críticas.

6.16 Qué debes recordar de este tema

  • Una interfaz es el punto de contacto entre componentes.
  • Un contrato define las reglas que esa interfaz debe respetar.
  • Una responsabilidad define qué debe hacer cada componente.
  • Las pruebas de integración verifican que los componentes usen interfaces y contratos correctamente.
  • Los cambios de contrato pueden romper consumidores aunque el productor funcione de forma aislada.
  • Responsabilidades claras hacen que las integraciones sean más fáciles de probar y mantener.

6.17 Conclusión

Las interfaces, los contratos y las responsabilidades son la base de una integración comprensible. Cuando están bien definidos, el equipo puede probar colaboraciones con mayor precisión y diagnosticar fallas con menos esfuerzo.

Una buena prueba de integración no solo ejecuta componentes juntos: verifica que cada parte respete el acuerdo que permite que el conjunto funcione.

En el próximo tema analizaremos los límites del sistema: qué incluir y qué dejar fuera de una prueba de integración.