Pruebas de contratos de cara al consumidor (DbC - Design by Contract)
La estructura de microservicios ha sido una tendencia que varias empresas han adaptado a esta práctica en los últimos años. Con los microservicios, los grandes proyectos de software se fragmentan en unidades o elementos más pequeños, desarrollados de forma autónoma por diferentes equipos.
Cada uno de estos elementos distintos tiene una responsabilidad específica y tiene su propia infraestructura para que los cambios puedan probarse e implementarse de forma independiente, lo que muestra numerosas ventajas en comparación con la arquitectura monolítica. Sin embargo, este nuevo enfoque también conlleva nuevos obstáculos para las pruebas.
En este artículo, exploraremos por qué las técnicas de prueba tradicionales pueden no ser las más adecuadas para evaluar microservicios y cómo podemos probarlas de manera eficiente mediante pruebas por contrato.
La adopción de numerosos servicios independientes más pequeños aporta ventajas, pero, por otro lado, plantea desafíos en comparación con los enfoques de prueba convencionales, ya que la verificación de la integración se vuelve compleja a medida que aumentan los puntos de conexión entre diferentes servicios.
Las técnicas de prueba convencionales, como la integración integral, pueden parecer la mejor opción, pero estas pruebas se vuelven problemáticas al evaluar microservicios ya que son lentas, propensas a errores y difíciles de mantener, lo que lleva a un proceso de retroalimentación lento. Además, requiere entornos de extremo a extremo adecuados en los que los servicios se integren entre sí con los datos de prueba necesarios, configuraciones, etc.
Si estamos tratando con sólo dos servicios, esta forma de prueba puede ser adecuada, pero imaginemos cuando hay cientos o miles de servicios diferentes comunicándose, como Amazon o Netflix. ¿Cómo asegurar que todas las modificaciones realizadas no causen problemas en otros servicios? ¿Cómo garantizar la eficiencia en el mantenimiento de los datos de prueba?
Las pruebas de integración de un extremo a otro requieren que los entornos de prueba adecuados estén conectados entre sí, pero implementar este enfoque para microservicios puede terminar generando más desafíos que beneficios. En cambio, si optamos por pruebas aisladas y creamos simulaciones de dependencias externas, las pruebas sin duda serán rápidas y fáciles de mantener.
Sin embargo, ¿qué confianza tenemos en que la simulación de dependencias refleja con precisión el comportamiento real del servicio? ¿Cómo podemos solucionar este problema? Afortunadamente, existe otra capa de pruebas que podemos aplicar para ayudar a evaluar los microservicios de manera eficiente.
Las pruebas de contrato significan que comparamos nuestra API con un conjunto de expectativas (contratos). Esto significa que queremos verificar que al recibir una llamada específica, nuestro servidor proveedor de API devolverá los datos que especificamos en la documentación. A menudo no tenemos información precisa sobre las necesidades de nuestros consumidores de API.
Para superar este problema, los consumidores pueden establecer sus expectativas como simulaciones que utilizan en pruebas unitarias, creando contratos que esperan que cumplamos. Podemos juntar estas simulaciones y verificar que nuestro proveedor devuelva datos iguales o similares cuando se llama de la misma manera que está configurada la simulación, esencialmente probando el límite del servicio. Este enfoque se denomina prueba de contrato impulsada por el consumidor.
La ventaja de las pruebas de contrato es que no hay necesidad de entornos integrados, ya que las expectativas del cliente se registran en este contrato y se validan con un servicio de proveedor simulado. Luego, el contrato se carga en Pact Broker y luego el proveedor ejecuta su prueba y verifica que todo lo escrito en el contrato sea correcto.
Las pruebas de contrato, aunque utilizamos un servicio simulado para validar nuestras pruebas, aún resuelven los problemas de implementar cambios rotos en producción, ya que se notifica a los clientes cuando el proveedor implementa un cambio que causa que el contrato se rompa y viceversa.
Al igual que los contratos o contratos de la vida real, si es necesario realizar un cambio en el contrato, se notificará a ambas partes y los cambios solo se realizarán cuando todos estén en la misma página y lo aprueben.
Arquitectura básica de la prueba de contrato
Limitaciones de la prueba de contrato
De todos modos, no puede utilizar pruebas de contrato solo cuando pruebe microservicios y no reemplace las comunicaciones reales entre diferentes equipos. Las pruebas de contrato no reemplazan las pruebas funcionales, ya que no probamos el comportamiento de nuestros servicios.
Tampoco es recomendable utilizar pruebas de contrato si tiene una API pública, porque no sabe cuántos clientes tiene y cómo utilizan su servicio.
Terminología abordada
- Consumidor - Servicio que consume datos de un proveedor;
- Proveedor - Servicio que ofrece datos al consumidor;
- Contrato/Pacto - Documento base del contrato entre el consumidor y el proveedor (normalmente en formato JSON que captura qué solicitudes necesita el consumidor del proveedor y qué tipos de datos, códigos de estado y respuestas devolverá el proveedor);
- Pact Broker - Servicio alojado que almacena todo el contrato (canal de comunicación entre consumidores y proveedores).
Cómo implementarla
Para implementar la prueba de contacto, utilizaremos la herramienta Pact de contrato de código aberto. En el siguiente enlace puedes ver paso a paso cómo funciona la metodología: Cómo funciona la prueba de contrato Pact.
Nos ofrece todo lo que necesitamos para crear, distribuir y validar contratos dentro de un sistema. Admite la mayoría de los lenguajes habituales y el uso de múltiples lenguajes en una arquitectura. Pact cubre el panorama de contratos impulsados por el cliente y automatiza la mayor parte del proceso.
El flujo comienza cuando el cliente define las pruebas unitarias: ¿qué respuesta espera el cliente en qué estado? Es importante señalar que el cliente debe probar la interacción y no la funcionalidad del proveedor. Si el cliente puede procesar una respuesta pero la prueba falla, probablemente esté probando la funcionalidad del proveedor. Esto debe probarse con pruebas unitarias del proveedor.
Por ejemplo, considera un servicio de validación que devuelve verdadero o falso según la entrada. Un cliente puede probar si el servicio de validación devuelve falso cuando la entrada excede una cierta cantidad de caracteres, pero el servicio de validación es responsable de este límite de caracteres y debe verificarse en sus pruebas unitarias y no en el contrato.
Por cada cambio que realice el cliente, se creará un contrato y se enviará al mediador de Pact. Cuando el proveedor realiza un cambio, todos los contratos del cliente serán recuperados del mediador. Un servidor simulado reproducirá solicitudes de contrato con contratos. Si las respuestas están en línea con el contrato, el proveedor puede implementar los cambios.
Arquitectura de la prueba de contrato con Pact Broker
Manos a la obra con Pact
La principal interfaz del consumidor es la clase PactV3 y las exportaciones MatchersV3 del paquete @pact-foundation/pact, de modo que el consumidor pueda escribir una prueba de API y definir sus suposiciones y necesidades de su(s) proveedor(es) de API. Al realizar pruebas unitarias de nuestro cliente API con Pact, se producirá un contrato que podremos compartir con nuestro proveedor para confirmar estas suposiciones y evitar cambios dañinos.
En este ejemplo, probaremos nuestro cliente User API, responsable de comunicarse con UserAPI a través de HTTP. Actualmente, tiene un único método GetUser(id) que devolverá un Usuario.
Las pruebas de pacto tienen algunas propiedades importantes como se demuestra a continuación.
Prueba del lado del consumidor
Prueba del lado del proveedor
La interfaz principal del proveedor es la clase Verifier del paquete @pact-foundation/pact. Una prueba de proveedor toma uno o más archivos de pacto (contratos) como entrada y Pact verifica si su proveedor cumple con el contrato.
En el caso más simple, puede verificar un proveedor como se muestra a continuación usando un archivo de pacto local, aunque en la práctica normalmente usaría un Pact Broker como se mencionó anteriormente para administrar sus contratos y el flujo de trabajo de CI/CD.
Conclusión
Las pruebas de contratos de cara al cliente son un concepto muy poderoso que podemos utilizar no solo para verificar la seguridad de los límites del servicio, sino también para diseñar y simplificar nuestras API. Comprender cuáles son los requisitos de los clientes nos ahorra muchas conjeturas al planificar nuestras tareas y escribe nuestro código. Además, es más fácil y rápido que configurar pruebas de integración adecuadas entre servicios, ya que no necesitamos tener dos servicios activos comunicándose entre sí.
Si no controlamos a todos los consumidores de nuestras API, las necesidades exactas de nuestros consumidores pueden perderse en la traducción. Incluso si nuestras pruebas de integración detectan el problema, es posible que no sepamos si detectamos un error del consumidor o si no cumplimos nuestros contratos correctamente.
Probablemente no desee detener un trabajo de CI/CD cuando falla la verificación de un contrato, porque un error tipográfico en una sola simulación de cliente podría impedirle publicar una nueva versión. Sin embargo, puede resultar útil descubrir rápidamente por qué se produjo un error con solo observar el estado de verificación de un contrato.
Pact y Pact Broker son herramientas impresionantes para pruebas de contratos de cara al cliente y pueden ser parte del conjunto de herramientas de cualquier desarrollador que trabaje con sistemas distribuidos. Si tuviera algunas capacidades de aserción más detalladas, podríamos reemplazar algunos casos de prueba que actualmente solo se pueden verificar mediante pruebas de integración complejas.
Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.