Contract Testing con Pact - La guía definitiva
19 de diciembre de 2024
Este artículo resume qué es el Contract Testing y las diferencias entre las dos principales estrategias de testing. Después de leerlo, tendrás (esperemos) suficiente información para determinar si una de ellas es una buena opción para tu proyecto.
El Contract Testing se centra en verificar las interacciones entre distintos componentes, ya sea en una arquitectura de microservicios (el contexto más habitual) o en cualquier otro tipo de entorno distribuido. A diferencia de las pruebas de integración tradicionales, no comprueba todo el sistema en su conjunto. En su lugar, valida que cada servicio (consumidor y proveedor) se adhiere a un "contrato" mutuamente acordado que define cómo se comunican.
El objetivo principal es garantizar que los cambios en un servicio no rompan la funcionalidad de otro.
Ventajas en el ciclo de desarrollo
El Contract Testing desempeña un papel crucial en la mejora del ciclo de vida del desarrollo de software:
-
Mejora de la agilidad
Los equipos pueden desarrollar y lanzar servicios de forma independiente sin esperar a la integración completa del sistema. Esta autonomía permite realizar iteraciones más rápidas y reducir los plazos de entrega. -
Aumenta la confianza en la integración
Aumenta la confianza en que los cambios en un servicio no afectarán inesperadamente a otros, reduciendo la necesidad de extensas pruebas manuales de integración. -
Mejora de la calidad global
"Shifting left" ayuda a detectar errores de integración en una fase temprana del proceso de desarrollo, evitando que los problemas lleguen a entornos posteriores. Este enfoque proactivo conduce a versiones más estables y a una experiencia más fluida para los desarrolladores.
¿Quieres más detalles sobre el impacto que puede tener en tu negocio? Echa un vistazo a nuestro artículo sobre el ROI de Contract Testing.
Tipos de Contract Testing
Consumer Driven
Con esta estrategia, el consumidor dirige la metodología (quién lo iba a esperar, dado el nombre...). El consumidor define las expectativas de la interacción. El contrato especifica las peticiones que el consumidor enviará y las respuestas que espera del proveedor, verificando que es capaz de gestionar las respuestas dadas. Los contratos se generan durante la fase de creación y prueba, en la que el marco de Pact pone en marcha un servidor simulado para validar las interacciones definidas. Si todo funciona como se espera, se genera un archivo de contrato.
Por otro lado, el proveedor es responsable de verificar cada uno de los contratos relacionados con él. El proveedor recupera los contratos relevantes de PactFlow (u otro Pact Broker), y las librerías Pact los utilizan durante la fase de construcción. El proceso implica iniciar un simulacro de consumidor que ejecutará las peticiones definidas contra el código real del proveedor, y verificar que las respuestas son las esperadas.
Puntos clave
-
Los consumidores definen los contratos como "contratos mínimos viables".
-
El proveedor debe incluir un código relacionado con la verificación en sus clases de prueba para garantizar que se cumplen estos contratos.
Este planteamiento hace recaer en el consumidor la responsabilidad de definir lo que necesita, y el proveedor adapta su aplicación para satisfacer esas expectativas.
Casos prácticos
En escenarios donde tienes control sobre ambos lados de la comunicación, como una arquitectura interna de microservicios, valoras cada componente, sabiendo no solo sobre quién consumen sino también quién los consume.
Buscamos que los componentes funcionen como un equipo, aunque estén gestionados por equipos diferentes.
Bi-Directional
En este caso, el nombre no se explica por sí mismo. En el lado del consumidor, nada cambia; el marco sigue esperando el mismo proceso: definir las expectativas y ejecutar pruebas durante la fase de construcción y pruebas. El contrato se publica en PactFlow (nota: este enfoque no está soportado por el broker de pactos OSS, al menos no todavía).
La principal diferencia radica en el proveedor. Con las pruebas bidireccionales, el proveedor no necesita añadir código de prueba. En su lugar, se espera que el proveedor (o cualquier otro agente en su nombre) publique una Especificación OpenAPI (OAS) a PactFlow. Esta OAS debe ser válida (ya sea generada a partir de código o, si eso no es posible, al menos validada utilizando cualquier herramienta de prueba de su elección). PactFlow confiará en el equipo del proveedor para mantener esta OAS como la fuente de la verdad.
La verificación la realiza el propio PactFlow, comparando el contrato de pacto publicado por el consumidor con la especificación OAS del proveedor.
A partir de este momento, nada cambia. Los flujos de trabajo, la automatización y otros procesos siguen siendo los mismos.
Puntos clave
-
Los consumidores siguen definiendo los contratos como "contratos mínimos viables".
-
El proveedor no necesita implementar código de prueba específico, simplemente necesita tener su OAS en PactFlow.
Casos prácticos
Este enfoque tiene sentido cuando no se tiene (o no se quiere tener) control sobre la base de código del proveedor. Por ejemplo, podría tratarse de una API que se integra con demasiados consumidores como para hacer viables las pruebas personalizadas, tal vez una API heredada que ya no evoluciona, o incluso un componente de terceros. El objetivo es ofrecer una alternativa a las pruebas dirigidas al consumidor en situaciones en las que la participación directa del proveedor es limitada.
Opinión personal
Siempre que sea posible, opte por las pruebas orientadas al consumidor. En mi experiencia, aporta más valor. El conocimiento compartido y la mayor integración que fomenta son inestimables.
Flujo de trabajo
Consumer Driven
-
El consumidor define las expectativas en su código base.
-
Durante la fase de construcción y prueba, el marco Pact inicia un proveedor simulado para probar las expectativas utilizando peticiones reales.
-
Si las pruebas se superan con éxito, se genera un archivo Pact (en formato JSON) y se publica en PactFlow o en el pact-broker.
Del lado del proveedor (proceso independiente):
-
El proveedor comienza su proceso de construcción. Descarga todos los contratos relacionados de PactFlow o del intermediario de Pact durante la fase de prueba.
-
Para cada contrato, el marco Pact inicia un servicio simulado de consumo y valida las expectativas utilizando solicitudes reales.
-
Se publica una comprobación de verificación en PactFlow para los contratos que han sido verificados.
Bi-Directional
-
El consumidor define las expectativas en su código base.
-
Durante la fase de construcción y prueba, el marco Pact inicia un proveedor simulado para probar las expectativas utilizando peticiones reales.
-
Si las pruebas se superan con éxito, se genera un archivo Pact (en formato JSON) y se publica en PactFlow.
Del lado del proveedor (proceso independiente):
-
El proveedor publica su Especificación OpenAPI (OAS) a PactFlow, ya sea generada a partir de código (preferido) o validada utilizando cualquier herramienta de prueba de su elección (este paso no está cubierto, ya que no es parte de la prueba de contrato en sí).
-
PactFlow verificará la compatibilidad entre los contratos publicados y la OAS.
Codificación
Consumer Driven
En el lado del consumidor, tendrás varios pares como los que se muestran en el siguiente código. @Pact se utiliza para definir las expectativas, @PactTestFor se utiliza para probar las expectativas definidas, y el método @BeforeEach asegura que nuestras pruebas están apuntando al servidor falso iniciado por el framework.
Código del consumidor
@SpringBootTest
@ExtendWith(PactConsumerTestExt.class)
clase StudentProviderTest {
public static final String STUDENT_1_EXISTS = "el estudiante con ID 1 existe";
private StudentService studentService;
@Pacto(consumidor = "consumidor", proveedor = "estudiante-proveedor")
public V4Pact getStudentWithId1(PactDslWithProvider builder) {
return builder.given(ESTUDIANTE_1_EXISTE)
.uponReceiving("obtener un alumno existente")
.path("/estudiantes/1")
.method("GET")
.willRespondWith()
.status(200)
.headers(Map.of("Content-Type", "application/json"))
.body(newJsonBody(objeto -> {
object.numberType("id", 1L);
object.stringType("nombre", "Nombre falso");
object.date("nacimiento", "aaaa-MM-dd", LocalDate.parse("2000-01-01"));
object.numberType("créditos", 30);
object.stringMatcher("email", Regex.EMAIL, "some.email@sngular.com");
object.object("dirección", dirección -> {
address.stringType("calle", "123 Main St");
address.stringType("ciudad", "CualquierCiudad");
address.stringType("zipCode", "12345");
});
object.minArrayLike("cursosinscritos", 2, curso -> {
course.stringType("courseName", "Introducción a la informática");
course.stringType("profesor", "Dr. Tech");
course.numberType("créditos", 3);
});
}).build())
.toPact().asV4Pact().get();
}
@AntesDeCada
void setup(MockServer mockServer) {
RestTemplate restTemplate = new RestTemplateBuilder().rootUri(mockServer.getUrl()).build();
studentService = new StudentService(restTemplate);
}
@Prueba
@PactTestFor(pactMethod = "getStudentWithId1")
void getStudentWhenStudentExist() {
Estudiante esperado = getStudentSample();
Estudiante estudiante = studentService.getStudent(1L);
assertDetallesDelEstudiante(esperado, estudiante);
}
}
Código del proveedor
Mientras tanto, la parte proveedora tendría que añadir pruebas para cubrir todos los estados definidos en sus contratos con consumidores (como "Student 1 exists" en nuestro ejemplo).
@PactBroker
@Provider("estudiante-proveedor")
@SpringBootTest()
clase StudentProviderVerificationTest {
public static final String STUDENT_1_EXISTS = "el estudiante con ID 1 existe";
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void verifyPact(PactVerificationContext context) {
context.verifyInteraction();
}
@AntesDeCada
void setUp(PactVerificationContext context) {
MockMvcTestTarget testTarget = new MockMvcTestTarget();
testTarget.setControllers(studentController);
testTarget.setControllerAdvices(customExceptionHandler);
context.setTarget(testTarget);
}
@Estado(ESTUDIANTE_1_EXISTE)
public void alumno1Existe() {
Estudiante uno = createFakeStudent(1L);
when(studentRepository.findById(1L)).thenReturn(Optional.of(one));
when(studentRepository.findAll()).thenReturn(List.of(one));
}
}
Bi-Directional
El mismo código de ejemplo del consumidor es válido para la opción bidireccional. No hay ningún cambio en la forma de implementar, utilizar, automatizar o desplegar los contratos en el lado del consumidor.
En cuanto al proveedor, no tendrás que añadir código de prueba a tu código base. Recuerda que solo se requiere la especificación OpenAPI.
Idealmente, el mejor enfoque (IMHO) es generarlo directamente desde tu código usando herramientas como el springdoc-openapi-maven-plugin o cualquier otra herramienta de tu elección. También es válido generarlo externamente y validarlo utilizando herramientas de prueba como ReadyAPI, RestAssured, Dredd o Postman. Puedes encontrar un montón de ejemplos y documentación en la web de PactFlow:
Automatización (CI)
Tenemos un par de artículos detallados que cubren los flujos de trabajo y las implicaciones de CI/CD para el enfoque orientado al consumidor, pero también se aplica al bidireccional. Siéntase libre de profundizar en ellos:
Consumer Driven
Puntos clave::
-
Los constructores de consumidores publicarán sus contratos en PactFlow.
-
Los proveedores descargarán los contratos relacionados de PactFlow para validarlos y publicar los resultados.
-
Can-I-Deploy y otros controles de calidad existen tanto para consumidores como para proveedores, como todos sabemos.
Tu trabajo consistirá en orquestar este proceso: gestionar cómo se etiquetan, organizan y filtran para su descarga los contratos publicados/descargados.
Esta estrategia da prioridad al consumidor. Lo ideal es que cada cambio comience en el lado del consumidor. Sin embargo, esto no significa que el proveedor dependa totalmente de las hojas de ruta de los consumidores. Cada parte puede evolucionar de forma independiente, aunque la evolución de los contratos esté impulsada por los consumidores.
Ejemplo de pasos básicos para el consumidor:
Para el proveedor, la verificación se realiza normalmente delegando en el plugin de pacto ejecutado dentro de la compilación:
Bi-Directional
Puntos clave:
-
Los constructores de consumidores publicarán sus contratos en PactFlow.
-
El proveedor no descarga ningún contrato de PactFlow. Su única responsabilidad es publicar la especificación OpenAPI.
-
Can-I-Deploy y otros controles de calidad existen tanto para consumidores como para proveedores, como todos sabemos.
Como puedes ver, la única y muy importante diferencia es sólo la construcción del proveedor. No descarga los contratos y los valida usando su código y pruebas. El proveedor sólo publica el OAS, y PactFlow hará su magia para comparar ese OAS con el contrato y comprobar la compatibilidad.
Con este enfoque, un ejemplo de creación de un proveedor tendría este aspecto:
Aplicar simultáneamente ambas técnicas es muy fácil y cómodo. |
---|
Desafíos
Infraestructura y requisitos
Aunque la configuración técnica es sencilla (al menos en teoría...), la realidad a menudo implica navegar por complejidades organizativas y técnicas.
Necesitarás configurar PactFlow (o pact broker) como un componente central de tu SDLC. Considéralo tan crítico como tu herramienta CI/CD, ya que se convierte en el centro de gestión y verificación de contratos entre servicios, desempeñando un papel fundamental a la hora de permitir o bloquear despliegues.
Ten mucho cuidado a la hora de diseñar e implementar tus procesos de automatización. Aunque los pasos básicos, como la publicación de contratos, su verificación y la realización de comprobaciones can-i-deploy son esenciales, la verdadera complejidad suele residir en garantizar el etiquetado, el versionado, el filtrado y la organización adecuados de los contratos. Estos aspectos son cruciales para mantener la claridad, escalabilidad y eficiencia a medida que crece el sistema.
El artículo Contract Testing Workflows es una excelente referencia para este asunto.
Complejidad de la adopción
Uno de los retos más importantes, especialmente en las grandes organizaciones, es impulsar la adopción. Promover esta práctica requiere un esfuerzo continuo de relaciones con los desarrolladores (DevRel), que incluye educar a los equipos, proporcionar apoyo y ofrecer formación para garantizar la alineación entre los departamentos. La transición a un enfoque que dé prioridad a las pruebas por contrato también puede implicar la resistencia de equipos acostumbrados a las pruebas de integración tradicionales o que dudan en invertir tiempo en aprender nuevas metodologías.
En la sección "Incorporación de equipos" de nuestro artículo "Pruebas y desarrollo de contratos" encontrarás algunas ideas útiles.
Sincronización de contratos
La evolución de las versiones de los contratos resulta más sencilla con el tiempo, a medida que los equipos se familiarizan con el marco. Sin embargo, el verdadero reto consiste en gestionar situaciones específicas o evoluciones de servicios que requieren un tratamiento personalizado. La clave es ser estricto con la metodología y permitir al mismo tiempo las personalizaciones necesarias.
Aunque todos conocemos la teoría básica, es inevitable que surjan casos especiales. Echa un vistazo a los ejemplos tratados en el artículo Contract Testing & CI para conocer cómo gestionarlos.
Pact Broker vs PactFlow
¿Es suficiente la versión OSS gratuita (Pact Broker) o es necesario PactFlow? La respuesta, como siempre, es "depende".
Pact Broker (la versión de código abierto) funcionará en muchos contextos. Supondrá una gran mejora para tu conjunto de pruebas y te proporcionará la mayoría de las ventajas de las pruebas de contratos. Sin embargo, no permite realizar pruebas bidireccionales. Si esa característica es crucial para ti, o si necesitas apoyo comercial, entonces la decisión de optar por PactFlow se hace más clara.
A continuación encontrarás un cuadro que resume las principales diferencias entre las dos opciones para ayudarte a tomar una decisión con conocimiento de causa:
Pact Broker | PactFlow | |
---|---|---|
Consumer Driven) | ✅ | ✅ |
Bi-Directional | 🟥 | 🟥 |
TC aumentada por IA | 🟥 | ✅ |
Formatos admitidos | Pact | Pact + OpenAPI |
Integración con SwaggerHub | 🟥 | ✅ |
Hosting | Autoalojado | SaaS o autoalojado |
Funciones, gestión de usuarios, equipos, etc | 🟥 | ✅ |
Acceso y administración seguros (SAML) | 🟥 | ✅ |
Interfaz de usuario | Básico | Avanzado |
Ayuda | Sólo para la Comunidad | ✅ |
Tokens API | 🟥 | ✅ |
Secretos | 🟥 | ✅ |
Registros de auditoría | 🟥 | ✅ |
Conclusión
En el mundo actual centrado en las API, donde API-first es la norma, el Contract Testing ya no es opcional. Es una práctica fundamental para garantizar la solidez de tus sistemas y el éxito de tu estrategia digital.
Pact, como marco de código abierto, y PactFlow, como herramienta con licencia, son las principales opciones para implantar esta práctica. No importa cuál elija, la adopción de pruebas por contrato es siempre una victoria para tus equipos (¡y para tu negocio!). De nuevo, te animo a que consultes nuestro artículo sobre el ROI para obtener más información).
Gracias por leerme.
Nuestras últimas novedades
¿Te interesa saber cómo nos adaptamos constantemente a la nueva frontera digital?
18 de diciembre de 2024
Agilidad, complejidad y método empírico
10 de diciembre de 2024
Tecnologías rompedoras hoy, que redibujarán el mapa de la innovación en 2025
3 de diciembre de 2024
Desarrollo sostenible: Minimizando la huella digital y optimizando el consumo
2 de diciembre de 2024
El Acta Europea de Accesibilidad (EAA): hacia un presente digital más inclusivo