Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Test de Contratos y Test de Componentes: más al...

Test de Contratos y Test de Componentes: más allá de la pirámide estándar

🔥 ¿Y si te dijera que un error mío distribuyó cientos de CDs defectuosos y casi me cuesta el trabajo?
Ese día no solo aprendí lo que es el caos… también entendí por qué las pruebas de software bien diseñadas salvan carreras.
De esa experiencia —entre lágrimas, bugs y monolitos de VB6— nace este libro:
🧱 "Test de Contratos y dotNet: Más allá de la pirámide de testing", donde comparto cómo evitar esos errores, cómo estructurar pruebas útiles de verdad, y por qué los contract tests son tu mejor seguro de vida en microservicios.
📘 Disponible gratis, en español, con ejemplos reales, código, herramientas y estrategia.
¿Lo quieres? Aquí lo tienes.
#softwarecraftsmanship #testing #dotnet #devops #microservices #aprendizajes #contratos

Tweet

More Decks by Jose María Flores Zazo

Other Decks in Technology

Transcript

  1. Test de Contratos y Test de Componentes: más allá de

    la pirámide estándar Software Craftsman Solution Architect @ AVANADE GitKraken Ambassador Testing & Microservicios Versión 1.0.0, Marzo de 2025
  2. 2 2 Motivo de la elaboración de este documento (1/4)

    A lo largo de mi carrera como desarrollador y arquitecto, siempre comprometido con la entrega de software de alta calidad, he experimentado de primera mano cómo la complejidad de las pruebas puede resultar abrumadora incluso para los más experimentados. He formado parte de proyectos donde las pruebas se reducían a una simple lista de tareas, y de otros donde la estrategia de pruebas estaba tan bien diseñada que se convertía en una verdadera ventaja competitiva. Con el tiempo, he aprendido que comprender la diversidad de enfoques y cómo estos se complementan es crucial para garantizar la calidad del software. Este libro tiene como objetivo desmitificar el complejo mundo de las pruebas de software, presentando no solo la conocida pirámide de pruebas, sino también otras representaciones visuales como el diamante o el reloj de arena. Cada una de estas figuras proporciona una perspectiva única sobre cómo organizar y priorizar las pruebas dentro de un proyecto. Sin embargo, mi intención va más allá de explorar estas representaciones, que considero aspectos más triviales del testing. Mi verdadero enfoque es profundizar en las pruebas de componentes y contratos, áreas a menudo subestimadas pero que, en mi experiencia, aportan un valor significativo, incluso más que las pruebas de integración y unitarias por sí solas. He escrito extensamente sobre temas de testing, y te recomendaría revisar esos escritos antes de sumergirte en este documento, para que puedas obtener el máximo provecho de los conceptos y estrategias que aquí presento.
  3. 3 3 Motivo de la elaboración de este documento (2/4)

    • La diferencia entre QA y QC. • Vuelta a los orígenes: Testing. • Trunk-Based Development (TBD) y relación con los Test. • GitFlow y relación con los Test. • Azure De 0 a 100 con Azure Load Testing Azure Chaos Studio y JMeter. • UI test con Selenium en Visual Studio. • DAMP vs DRY en C#: Encontrando el Equilibrio Perfecto. • Acelerando el Desarrollo de Software con Apicurio y Kiota de Microsoft. • El Principio del 80/20 en el Contexto de DDD y TDD. • Git mejores prácticas: Hooks. • Accesibilidad en Web. • Y deferentes Benchmark que también son consideraros test por que nos ayudan a demostrar con datos por qué escogemos o no un tipo de tecnología. Como pueden ver, dedicar tanto tiempo a las pruebas de software no es casualidad; se debe a una experiencia temprana en mi carrera como desarrollador junior que dejó una marca indeleble en mi enfoque profesional. Recuerdo haberme enfrentado un desafío cuando tuve que modificar una función con una deuda técnica considerable: un monolito de cientos de líneas (no se si al final llegó a más del millar) de código en Visual Basic 6, repleto de estructuras complejas como ifs, cases y gotos. Este código era tan intrincado que prácticamente requería un experto (un silo y los que me conocen sabe que estoy en contra de ellos) solo para comprenderlo.
  4. 4 4 Motivo de la elaboración de este documento (3/4)

    Durante una modificación, inadvertidamente introduje un error que llevó a la distribución de cientos de CD-ROMs defectuosos. El proceso de copiar, empaquetar y enviar estos discos, tanto los que contenían el programa con el error como las versiones corregidas, junto con las cartas de disculpa correspondientes, fue agotador. Para empeorar la situación, muchos usuarios ya habían instalado el software defectuoso. El caos que se desató a partir de este incidente se convirtió en un verdadero punto de inflexión en mi carrera. En mi búsqueda de soluciones, decidí recurrir a las mejores prácticas de desarrollo, proponiendo dos iniciativas clave: modularizar el código y aislar casos de uso mediante patrones de diseño, como el patrón Strategy, que era posible implementar de manera aproximada incluso en Visual Basic 6. Además, sugerí la creación de bases de datos específicas para ejecutar pruebas automáticas desde una interfaz de línea de comandos, cubriendo todas las combinaciones y escenarios posibles. Sin embargo, mi propuesta fue recibida con escepticismo y la respuesta que obtuve fue: "Eso es demasiado costoso y nos llevará mucho tiempo, y nunca vamos a poner en práctica esto por que tenemos que mantenerlo". En ese momento, mi puesto estuvo en riesgo por dos motivos: primero, debido al error que había introducido, que tuvo un impacto económico (empresa y clientes), además del reputacional; y segundo, tras unos días de reflexión, por desafiar el statu quo. Mi propuesta implicaba una crítica indirecta a mis superiores, al sugerir que con las herramientas adecuadas podríamos prevenir problemas similares en el futuro. Fue una experiencia que casi me llevó a abandonar algo que realmente me apasiona. Recuerdo que incluso se me escaparon algunas lágrimas al darme cuenta del alcance de mi error, la bronca y del trabajo deficiente que había realizado.
  5. 5 5 Motivo de la elaboración de este documento (4/4)

    Esta experiencia me llevó a profundizar en el mundo del testing y a comprender cómo las pruebas adecuadas pueden prevenir problemas similares en el futuro. Por ello, comparto mis conocimientos y experiencias a través de este libro, con la esperanza de que puedan beneficiarse de las lecciones que aprendí de la manera más difícil. “Habla lo que piensas, aunque tu voz tiemble” Frase, atribuida a veces a Maggie Kuhn y que habla sobre el edadismo. P.D.: Incluso cuando era un junior, llegué a borrar infraestructura en producción, lo que me impulsó a desarrollar scripts para automatizar el despliegue de software y gestionar las configuraciones necesarias. En ese entonces, ni siquiera sabía qué era DevOps. Parafraseando a Platón, considero que DevOps es una reminiscencia del pasado, algo que, de alguna manera, ya conocíamos. Estas experiencias han enriquecido mi fase de aprendizaje, derivadas de la inconsistencia que existía hace más de 25 años. Aunque es cierto que el aprendizaje nunca termina y siempre somos juniors en algún aspecto. En 2000/2001, cuando no tenía acceso a IRC y aún menos a Internet, tuve la oportunidad de descubrir libros de gran calidad gracias a Cocodrilo Libros. Fue entonces cuando pude comprar una re-edición de Design Patterns: Elements of Reusable Object-Oriented Software y libros como Modern C++ Programming with Test-Driven Development. Esta librería marcó un hito en mi aprendizaje, en mi bolsillo y en mi estantería.
  6. 6 6 ¿A quién va dirigido este documento? Está dirigido

    a quienes buscan crear software de alta calidad y comprender quién es responsable en cada etapa del proceso de desarrollo. Ya sea que tengas experiencia o estés comenzando, aquí encontrarás un recurso claro y comprensible en español que aborda estos temas de manera formal. El debate entre Aseguramiento de la Calidad (QA) y Control de la Calidad (QC) es crucial en el ámbito del software. En mi experiencia, el perfil de QA a menudo se malinterpreta. El verdadero valor radica en QC, que se enfoca en el resultado y en la detección de errores antes de que el software llegue al cliente. Esto no solo mejora la calidad del producto, sino que también aumenta la eficiencia del equipo de desarrollo. QA debería ser una parte integral del desarrollo desde el inicio, incorporando prácticas de testing que van desde test unitarios hasta el Test-Driven Development (TDD) para asegurar la calidad desde la primera línea de código. Mientras tanto, QC verifica que el producto final cumpla con los requisitos especificados, detectando y corrigiendo errores de manera efectiva. Al centrarnos en QC y adoptar un enfoque que integre QA desde el inicio, podemos lograr productos de mayor calidad y fomentar una cultura de mejora continua. Esto beneficia tanto al equipo como al negocio, asegurando que los proyectos no solo cumplan, sino que superen las expectativas del cliente. Para una perspectiva respaldada por estándares internacionales, puedes consultar este artículo que explica la diferencia entre QA y QC, conforme a la ISO: La diferencia entre QA y QC.
  7. 7 7 ¿Cómo está organizado este documento? (1/2) • Antes

    de empezar ▪ Introducción. ▪ La Torre del Testing: ¿por qué una torre y no una pirámide?. • Test de Contratos ▪ Introducción. ▪ ¿Qué son las Pruebas de Contratos?. ▪ Importancia de las Pruebas de Contratos. ▪ Desafíos de las Pruebas de Contratos. ▪ Ejemplo de Implementación (1ª Parte) con Pact. ▪ Pasos para ejecutar Pruebas de Contratos. ▪ Antipatrón: Componentes de Contratos. ▪ Herramientas. ▪ DIY (Do It Yourself). ▪ Flujo de trabajo con Apicurio. ▪ Flujo de trabajo Azure API Management. ▪ Ejemplo de Implementación (2ª Parte) con Pact DIY. ▪ Ejemplo de Implementación (2ª Parte) con Pact. ▪ ¿Convertir Pruebas de Integración en Pruebas de Contratos?. ▪ Estrategias de Versionado de Contratos y Gestión del Cambio.
  8. 8 8 ¿Cómo está organizado este documento? (2/2) • Test

    de Componentes ▪ Tipos de Pruebas. ▪ ¿Qué son las Pruebas de Componentes?. ▪ Importancia de las Pruebas de Componentes. ▪ Desafíos de las Pruebas de Componentes. ▪ Ejemplo de Implementación. ▪ ¿Cómo se integran en el flujo de trabajo?. • Anexos ▪ Anexo A: Glosario con definiciones técnicas. ▪ Anexo B. Antipatrones en Estrategias de Testing. ▪ Anexo C. Caja Negra vs Caja Blanca • Epílogo
  9. 10 10 Antes de empezar (2/6) Tipo de Prueba Descripción

    Herramientas en .NET & DevOps Coste Complejidad Automatización Pruebas Unitarias Verifican funciones o clases aisladas. Base de la calidad. xUnit, NUnit, MSTest, Moq Pruebas de Contrato Validan que proveedor y consumidor respetan la interfaz acordada. PactNet, DIY (JsonSchema), OpenAPI, Postman Pruebas de Componente Prueban un microservicio en aislamiento con stubs/fakes. WebApplicationFactory, WireMock.Net, SQLite in-memory Pruebas de Integración Validan interacción real con DB, colas, servicios. TestServer, Docker Compose, EF Core, REST Clients Pruebas Funcionales Evalúan funciones desde el punto de vista del negocio o usuario. SpecFlow, BDD, Postman, Playwright, Selenium Pruebas E2E Simulan escenarios completos con varios sistemas. Playwright, Selenium, Cypress, Postman CLI Pruebas de Aceptación Verifican cumplimiento de criterios del cliente o PO. Gherkin, SpecFlow, Validadores de reglas, pruebas GUI manuales Pruebas de Rendimiento Evalúan respuesta, escalabilidad y uso de recursos bajo presión. k6, JMeter, Azure Load Testing, Benchmark.NET Pruebas de Carga Simulan múltiples usuarios concurrentes para medir comportamiento. k6, JMeter, Azure Load Testing Pruebas de Humo Verifican que lo básico del sistema funciona tras cada build o deploy. HealthChecks, Postman CLI, Bash scripts, curl Pruebas de Regresión Validan que no se rompa funcionalidad previa tras cambios. Reutilizan tests unitarios, contrato, integración Pruebas de Caos Inyectan fallos en red, CPU, servicios para validar resiliencia. Azure Chaos Studio, Gremlin, ToxiProxy Pruebas Manuales Exploratorias Buscan errores inesperados con navegación libre del tester. Test plans, checklists, grabación de sesiones Pruebas de Usabilidad Validan experiencia de usuario, accesibilidad y comprensión. Observación, sesiones con usuarios reales, feedback directo Leyenda • Automatizadas: Ejecutables en CI/CD de forma autónoma. • Mixtas: Pueden automatizarse parcialmente, pero a menudo requieren intervención humana. • Manuales: Dependen exclusivamente de personas y no son automatizables. •Coste/Complejidad: Estimaciones relativas para equipos típicos .NET trabajando con microservicios: • Baja/a. • Media/o. • Alta. • Muy Alta.
  10. 11 11 Antes de empezar (3/6) La Torre del Testing:

    ¿por qué una torre y no una pirámide? La Torre de Testing es una metáfora que nació como una reflexión personal. Siempre había visto la pirámide de testing, el reloj de arena, el diamante... pero me unos modelos algo escuetos, rígidos o unidimensionales. Así que me propuse crear un modelo más expresivo, más técnico y a la vez visualmente claro: nació la torre. La torre transmite un mensaje muy claro: • Las pruebas deben escalarse por coste-beneficio. • No todas son obligatorias ni deben ejecutarse siempre. • Hay pruebas que conviene ejecutar en paralelo o condicionalmente. • No se trata de subir linealmente, sino de adaptar la estrategia al producto y su ciclo de vida. La base de la torre, como en arquitectura defensiva, es lo más sólido, fiable y esencial: las pruebas que deben ejecutarse siempre y en cada cambio. Son rápidas, automatizadas y con alto retorno. Desde ahí, se construye hacia arriba, estrechando —como en cualquier fortificación— en función del esfuerzo, la utilidad y el contexto.
  11. 12 12 Antes de empezar (4/6) A medida que subimos,

    encontramos bloques de pruebas más costosas, lentas o dependientes de recursos. En el segundo nivel se sitúan las pruebas de contrato, clave para garantizar que los servicios mantienen acuerdos estables entre sí sin requerir entornos completos para validarlo. En el tercer nivel, pruebas de componente e integración aportan confianza en cada servicio aislado o en su conjunto, ya con dependencias reales o simuladas. En muchos casos, estas capas se alimentan entre sí: si los contratos son fiables, las integraciones ganan solidez. La ventana central de la torre representa aquellas pruebas que pueden o deben ejecutarse en paralelo, especialmente en etapas de estabilización o pre-release. Aquí aparecen las pruebas de rendimiento, carga, caos, E2E o funcionales. No siempre son necesarias en cada build, pero sí cuando buscamos entender cómo se comporta el sistema bajo condiciones reales o extremas. Actúan como el gran ventanal de la torre: nos permiten ver la aplicación de forma amplia y en tiempo real. En la parte superior, coronando la torre, están las almenas. Aquí ubicamos pruebas excepcionales, manuales o estratégicas: usabilidad, exploratorias, aceptación. Se ejecutan cuando el contexto lo exige, cuando se quiere validar la experiencia real o cuando el juicio humano es irremplazable. Como en tiempos de guerra, las almenas están ahí para defender cuando hace falta, aunque no estén activas constantemente. Cada torre es diferente: algunas son más esbeltas (más automatización, menos validación manual), otras más robustas (mayor control, más validaciones humanas). Pero ninguna torre es recta, ni mucho menos invertida: eso colapsaría. Esta metáfora no es solo una herramienta visual. Es una guía para equipos que desean diseñar su estrategia de pruebas pensando en el coste, el riesgo, la automatización y la resiliencia. Una evolución natural, moderna y flexible de la pirámide clásica.
  12. 13 13 Antes de empezar (5/6) La Torre del Testing

    Nivel superior (las almenas, A): estratégicas / manuales / usuario • Pruebas de Usabilidad • Pruebas Manuales Exploratorias • Pruebas de Aceptación Ventana central (B): pruebas paralelas dentro de un release estable, dan visión de nuestra aplicación • Pruebas de Rendimiento • Pruebas de Carga • Pruebas de Caos • Pruebas E2E • Pruebas Funcionales Tercer nivel (bloque medio, C): pruebas de sistema • Pruebas de Integración • Pruebas de Componente Segundo nivel (bloque inferior, D) • Pruebas de Contrato Base de la torre (E): fundamentos automáticos • Pruebas Unitarias • Pruebas de Humo • Pruebas de Regresión A B C D E
  13. 14 14 Antes de empezar (6/6) Tipo de Prueba Qué

    hacen Por qué están abajo Por qué están a media altura Por qué están arriba Pruebas Unitarias Verifican funciones o clases individuales en aislamiento. Son muy rápidas, de bajo coste y permiten detectar errores desde el principio. Pruebas de Contrato Validan que proveedor y consumidor respetan la interfaz acordada. Se pueden automatizar, son más rápidas y precisas que muchas pruebas de integración. Ejecutadas continuamente. Pruebas de Componente Prueban un microservicio en aislamiento con stubs/fakes. Permiten probar lógica de negocio en contexto real sin necesitar otros servicios. Pruebas de Humo Verifican que lo básico del sistema funciona tras cada build o deploy. Bajo coste, automáticas, protegen contra errores evidentes. Pruebas de Regresión Validan que no se rompa funcionalidad previa tras cambios. Reutilizan tests existentes. De bajo coste si están automatizadas. Pruebas de Integración Validan interacción real con DB, colas, servicios. Más lentas que unitarias/componentes; necesarias en pipelines. Pruebas Funcionales Evalúan funciones desde el punto de vista del negocio o usuario. Valiosas pero su mantenimiento automático es costoso. Pruebas de Aceptación Verifican cumplimiento de criterios del cliente o PO. Se ejecutan antes del paso a producción. Pruebas E2E Simulan escenarios completos con varios sistemas. Costosas, lentas, se limitan a flujos críticos. Pruebas de Rendimiento Evalúan respuesta, escalabilidad y uso de recursos bajo presión. Se ejecutan solo en etapas críticas (preprod, nightly). Pruebas de Carga Simulan múltiples usuarios concurrentes para medir comportamiento. Alto coste; se usa en entornos controlados. Pruebas de Caos Inyectan fallos en red, CPU, servicios para validar resiliencia. Solo aplican en sistemas resilientes con alta madurez. Pruebas Manuales Exploratorias Buscan errores inesperados con navegación libre del tester. Se hacen puntualmente, si hay tiempo o sospechas de errores ocultos. Pruebas de Usabilidad Validan experiencia de usuario, accesibilidad y comprensión. Solo se realizan cuando se necesita validar experiencia real. Tipos de Prueba adaptado a la Torre de Pruebas
  14. 15 15 Test de Contratos (1/55) Introducción La arquitectura de

    microservicios se ha convertido en un paradigma dominante para el desarrollo de aplicaciones escalables y flexibles. A diferencia de las arquitecturas monolíticas tradicionales, los microservicios descomponen una aplicación en un conjunto de servicios pequeños e independientes que se comunican entre sí a través de interfaces bien definidas. Esta fragmentación permite a los equipos de desarrollo trabajar de manera más ágil y entregar características más rápidamente. Sin embargo, también introduce nuevos desafíos, particularmente en lo que respecta a la comunicación entre servicios. Contexto La arquitectura de microservicios se basa en la premisa de que cada servicio es autónomo y se ejecuta como un proceso independiente. Estos servicios interactúan entre sí para cumplir con los requisitos de negocio más amplios. Esta interacción es el "pegamento" que mantiene unida a toda la aplicación. Por ejemplo, un servicio de gestión de usuarios podría necesitar comunicarse con un servicio de facturación para verificar el estado de pago de un usuario. La confiabilidad de esta comunicación es crucial; cualquier fallo o malentendido puede llevar a interrupciones en el servicio, errores de datos o una mala experiencia del usuario.
  15. 16 16 Test de Contratos (2/55) Dado que cada servicio

    puede ser desarrollado y mantenido por diferentes equipos, posiblemente utilizando diferentes tecnologías, garantizar que todos los servicios "hablen el mismo idioma" se convierte en una tarea crítica. Aquí es donde las pruebas de integración tradicionales suelen mostrar sus limitaciones, ya que pueden ser complejas, lentas y difíciles de mantener en entornos de microservicios en constante cambio. Propósito Para abordar estos desafíos de comunicación, las pruebas de contratos emergen como una solución eficaz. A diferencia de las pruebas de integración completas, las pruebas de contratos se centran en verificar los acuerdos específicos (o contratos) entre un proveedor de servicios y sus consumidores. Piensa en estos contratos como un acuerdo formal sobre cómo un servicio debe comportarse cuando interactúa con otro. El propósito de este enfoque es doble. Primero, permite validar que las interacciones entre servicios se desarrollen según lo esperado desde las etapas más tempranas del desarrollo. Segundo, facilita el desarrollo y despliegue independiente de servicios al asegurar que cualquier cambio en un servicio no rompa la funcionalidad de otros servicios que dependen de él. Esto no solo reduce el riesgo de fallos en producción, sino que también mejora la velocidad y eficiencia del ciclo de desarrollo, permitiendo a los equipos moverse con la agilidad que las modernas prácticas de DevOps demandan.
  16. 17 17 Test de Contratos (3/55) ¿Qué son las Pruebas

    de Contratos? Este tipo de pruebas se centra en garantizar que los servicios interactúen conforme a un conjunto acordado de reglas o "contratos", asegurando así la comunicación efectiva y confiable entre ellos. Definición Las pruebas de contratos son un método para validar los acuerdos establecidos entre un proveedor de servicios y un consumidor. Estos acuerdos, o contratos, definan cómo debe comportarse el servicio proveedor cuando es consumido por otro servicio. Básicamente, un contrato especifica las expectativas del consumidor respecto a las solicitudes que puede hacer y las respuestas que debe recibir del proveedor. Por ejemplo, si un servicio de consumidor espera que un servicio de proveedor devuelva una lista de productos en formato JSON cuando se llama a un endpoint específico, este comportamiento esperado se documenta en un contrato. Las pruebas de contratos verifican que el proveedor cumple con estas expectativas, y que cualquier cambio en el proveedor no rompe la funcionalidad esperada por el consumidor.
  17. 18 18 Test de Contratos (4/55) Comparación con las Pruebas

    de Integración Tradicionales Las pruebas de contratos se diferencian significativamente de las pruebas de integración tradicionales. Mientras que las pruebas de integración suelen enfocarse en probar múltiples servicios en conjunto, asegurando que funcionen de manera integrada, las pruebas de contratos se centran en las interfaces y los acuerdos entre servicios individuales, sin necesidad de ejecutarlos todos juntos. Aspecto Pruebas de Contratos Pruebas de Integración Eficiencia Ofrecen una manera eficiente de asegurar que los servicios cumplan con los contratos establecidos sin tener que desplegar todo el sistema. Permiten detectar problemas de manera temprana en el ciclo de desarrollo, antes de que lleguen a producción. Requieren que múltiples servicios estén desplegados y funcionando simultáneamente, lo cual puede ser costoso en términos de recursos y tiempo. Reducción de Complejidad Simplifican el proceso de validación al centrarse únicamente en las expectativas entre un proveedor y un consumidor. Esto reduce la complejidad al evitar la necesidad de configurar y mantener entornos de prueba complejos. Pueden ser complejas y difíciles de mantener, especialmente en arquitecturas de microservicios donde los servicios cambian frecuentemente.
  18. 19 19 Test de Contratos (5/55) Importancia de las Pruebas

    de Contratos Las pruebas de contratos desempeñan un papel crucial en la gestión eficiente y efectiva de arquitecturas de microservicios. A continuación, se describen las razones clave por las que son esenciales. Detección Temprana de Problemas Una de las ventajas más significativas de las pruebas de contratos es su capacidad para identificar problemas de integración desde las primeras etapas del desarrollo. En un entorno de microservicios, donde los servicios son desarrollados y mantenidos por equipos diferentes, es fácil que surjan discrepancias entre lo que un servicio espera recibir y lo que otro proporciona. Las pruebas de contratos permiten a los equipos detectar estas discrepancias antes de que los servicios se desplieguen en producción, evitando sorpresas desagradables y costosas correcciones de último minuto. Al validar las interacciones entre servicios de manera temprana, se asegura que cualquier cambio en un servicio no afecte negativamente a otros, promoviendo un desarrollo más fluido y sin interrupciones. Desarrollo Independiente La capacidad de desarrollar y desplegar servicios de forma independiente es uno de los principales beneficios de la arquitectura de microservicios. Las pruebas de contratos facilitan este desarrollo independiente al establecer expectativas claras sobre las interacciones entre servicios.
  19. 20 20 Test de Contratos (6/55) Cada servicio puede desarrollarse

    y probarse en base a un contrato establecido, sin necesidad de esperar a que los demás servicios estén listos o disponibles. Esto acelera el ciclo de desarrollo, ya que los equipos pueden trabajar de manera autónoma y paralela, asegurando que los servicios se integrarán correctamente cuando finalmente se desplieguen juntos. Estabilidad y Confiabilidad Los contratos predefinidos actúan como un acuerdo formal entre los servicios sobre cómo deben interactuar. Esto reduce significativamente el riesgo de comportamientos inesperados en producción, ya que cada servicio está obligado a adherirse a un contrato que define claramente las expectativas de entrada y salida. Al minimizar el riesgo de errores de comunicación, se mejora la estabilidad y confiabilidad del sistema en su conjunto. Esto es especialmente importante en aplicaciones críticas donde los fallos pueden tener consecuencias graves. Integración en CI/CD Las pruebas de contratos se integran naturalmente en los pipelines de Integración y Entrega Continua (CI/CD), proporcionándoles un marco para la validación automática de interacciones entre servicios. Esta integración permite a los equipos recibir retroalimentación rápida sobre cualquier problema de compatibilidad que pueda surgir debido a cambios en el código. Cada vez que un servicio se actualiza, las pruebas de contratos pueden ejecutarse automáticamente para verificar que las interacciones con otros servicios siguen siendo válidas. Esto no solo mejora la calidad del software, sino que también aumenta la confianza en los despliegues frecuentes y reduce el tiempo necesario para llevar nuevas características al mercado.
  20. 21 21 Test de Contratos (7/55) Desafíos de las Pruebas

    de Contratos Aunque las pruebas de contratos ofrecen numerosos beneficios para garantizar la comunicación efectiva entre microservicios, también presentan ciertos desafíos que deben ser gestionados cuidadosamente para maximizar su efectividad. Mantenimiento de Contratos Uno de los principales desafíos de las pruebas de contratos es el mantenimiento continuo de los contratos a medida que los servicios evolucionan. En un entorno de microservicios, los servicios pueden cambiar con frecuencia debido a actualizaciones de características, optimizaciones o cambios en los requisitos de negocio. Cada uno de estos cambios puede requerir una actualización del contrato para reflejar nuevas expectativas o modificaciones en las interfaces de comunicación. El mantenimiento de estos contratos puede ser un proceso laborioso, especialmente cuando múltiples servicios están interconectados. Si los contratos no se actualizan adecuadamente, pueden volverse obsoletos, lo que puede llevar a falsos positivos o negativos durante las pruebas, disminuyendo su utilidad. Para mitigar este problema, es crucial establecer prácticas de gestión de contratos, como revisiones regulares y automatización de actualizaciones, para asegurar que los contratos siempre reflejen el estado actual de los servicios.
  21. 22 22 Test de Contratos (8/55) Complejidad de Herramientas Integrar

    las pruebas de contratos en los pipelines de Integración y Entrega Continua (CI/CD) puede ser complicado. Las herramientas utilizadas para las pruebas de contratos deben ser compatibles con la infraestructura existente y deben poder integrarse de manera fluida en el proceso de desarrollo y despliegue. Esto puede requerir una comprensión profunda de las herramientas, como Pact o Postman, y cómo se integran con otras tecnologías en uso. Además, las organizaciones a menudo utilizan una variedad de lenguajes de programación y plataformas, lo que puede complicar aún más la integración de herramientas de pruebas de contratos. Superar este desafío requiere una planificación cuidadosa y, a menudo, la adopción de herramientas que soporten entornos poliglota, lo que puede aumentar la complejidad y el costo inicial de implementación. Complejidad de Herramientas Otro desafío significativo es la simulación precisa de las dependencias externas durante las pruebas. Los servicios de microservicios rara vez operan de manera aislada; a menudo dependen de otros servicios o bases de datos externos para funcionar correctamente. Durante las pruebas de contratos, es esencial simular estas dependencias de manera que las pruebas sean realistas y reflejen el comportamiento del sistema en producción. Simular estas dependencias puede ser complicado, ya que requiere la creación de entornos de prueba que reproduzcan fielmente las condiciones de producción. Esto puede incluir la emulación de servicios externos, la gestión de datos de prueba y la configuración de respuestas simuladas. Sin embargo,
  22. 23 23 Test de Contratos (9/55) Simular estas dependencias puede

    ser complicado, ya que requiere la creación de entornos de prueba que reproduzcan fielmente las condiciones de producción. Esto puede incluir la emulación de servicios externos, la gestión de datos de prueba y la configuración de respuestas simuladas. Sin embargo, lograr este nivel de precisión es crucial para asegurar que las pruebas de contratos proporcionen resultados válidos y útiles
  23. 24 24 Test de Contratos (10/55) Ejemplo de Implementación (1ª

    Parte) con Pact Pact es una herramienta orientada a código para probar integraciones HTTP y de mensajes utilizando pruebas de contratos. Las pruebas de contratos aseguran que los mensajes entre aplicaciones cumplan con un entendimiento compartido documentado en un contrato. Sin pruebas de contrato, la única forma de garantizar que las aplicaciones funcionen correctamente juntas es mediante pruebas de integración costosas y frágiles. Un párrafo muy descriptivo que existe en la herramienta Pact describe muy bien que es una Prueba de Contrato: ¿Provocarías un incendio en tu casa solo para verificar si tu detector de humo funciona? Claro que no, en su lugar, utilizas el botón de prueba para asegurarte de que el detector cumple su función. Pact actúa como ese botón de prueba para tu código, permitiéndote verificar de manera segura que tus aplicaciones cooperarán correctamente sin necesidad de desplegar todo el sistema de antemano. Del mismo modo se aplica a las pruebas de integración. ¿Montarías un entorno completo con todos sus componentes solo para verificar la integración entre servicios? Esto implica un gran esfuerzo en términos de configuración y mantenimiento, y al final, muchas de las pruebas de integración, incluso cuando se utilizan contenedores, no son verdaderas pruebas de integración. A menudo, estás simulando o alterando elementos del entorno, lo que significa que los resultados pueden no reflejar con precisión cómo se comportarán los sistemas en un entorno de producción real.
  24. 25 25 Test de Contratos (11/55) Terminología de Pact En

    general, un contrato es entre un consumidor (por ejemplo, un cliente que quiere recibir algunos datos) y un proveedor (por ejemplo, una API en un servidor que proporciona los datos que el cliente necesita). En arquitecturas de microservicios, los términos tradicionales cliente y servidor no siempre son apropiados, por ejemplo, cuando la comunicación se logra a través de colas de mensajes. Por esta razón, nos adherimos a los términos consumidor y proveedor en esta documentación. Consumidor Proveedor 1. Las pruebas unitarias verifican su comportamiento contra el mock del proveedor 2. Las interacciones requeridas se capturan en un contrato entre sistemas 3. El contrato se comparte entre equipos para facilitar la colaboración, usando herramientas como Pactflow 4. Las solicitudes en el contrato se reproducen contra la API del proveedor y se verifican contra las expectativas del consumidor 5. Las pruebas unitarias del proveedor simulan otros sistemas, para que pueda ser probado en aislamiento
  25. 26 26 Test de Contratos (12/55) Contratos dirigidos por el

    consumidor Pact es una herramienta de pruebas de contratos dirigida por el consumidor, orientada a código, y generalmente es utilizada por desarrolladores y testers que codifican. El contrato se genera durante la ejecución de las pruebas automatizadas del consumidor. Una gran ventaja de este patrón es que solo se prueban las partes de la comunicación que realmente son utilizadas por el consumidor o consumidores actuales. Esto significa que cualquier comportamiento del proveedor no utilizado por los consumidores actuales es libre de cambiar sin romper las pruebas. Ejemplo Para configurar un servicio de proveedor en un entorno de desarrollo utilizando C#, primero necesitas tener un servicio API que sirva como proveedor. Supongamos que tienes un servicio que proporciona información sobre productos. dotnet new webapi -n ProductService cd ProductService
  26. 27 27 Test de Contratos (13/55) Define un endpoint en

    ProductsController que devuelva una lista de productos: [ApiController] [Route("[controller]")] public class ProductsController : ControllerBase { [HttpGet] public IActionResult GetProducts() { var products = new List<Product> { new Product { Id = 1, Name = "Laptop", Price = 999.99m }, new Product { Id = 2, Name = "Smartphone", Price = 499.99m } }; return Ok(products); } }
  27. 28 28 Test de Contratos (14/55) Ejecutamos el Proveedor: dotnet

    run Para definir y ejecutar una prueba de contrato desde el lado del consumidor, creamos un proyecto de prueba que use Pact para definir las expectativas del consumidor. dotnet new xunit -n ProductConsumerTests cd ProductConsumerTests Agregamos el paquete: dotnet add package PactNet.Windows Definimos el test:
  28. 29 29 Test de Contratos (15/55) using System.Collections.Generic; using System.Net.Http;

    using System.Threading.Tasks; using Xunit; using PactNet.Mocks.MockHttpService; using PactNet.Mocks.MockHttpService.Models; public class ProductApiConsumerPactTests : IClassFixture<PactBuilder> { private readonly IMockProviderService _mockProviderService; private readonly string _mockProviderServiceBaseUri; public ProductApiConsumerPactTests(PactBuilder pactBuilder) { _mockProviderService = pactBuilder.MockProviderService; _mockProviderServiceBaseUri = pactBuilder.MockProviderServiceBaseUri; _mockProviderService.ClearInteractions(); } [Fact] public void ItHandlesValidProductResponse() { // Preparar _mockProviderService .UponReceiving("A valid product request") .With(new ProviderServiceRequest { Method = HttpVerb.Get, Path = "/products" })
  29. 30 30 Test de Contratos (16/55) .WillRespondWith(new ProviderServiceResponse { Status

    = 200, Headers = new Dictionary<string, object> { { "Content-Type", "application/json; charset=utf-8" } }, Body = new[] { new { Id = 1, Name = "Laptop", Price = 999.99 }, new { Id = 2, Name = "Smartphone", Price = 499.99 } } }); // Actuar / Verificar var consumer = new ProductApiClient(_mockProviderServiceBaseUri); var products = consumer.GetAllProducts().Result; Assert.Equal(2, products.Count); _mockProviderService.VerifyInteractions(); } }
  30. 31 31 Test de Contratos (17/55) E implementamos el cliente

    consumidor: using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; public class ProductApiClient { private readonly HttpClient _client; public ProductApiClient(string baseUri) { _client = new HttpClient { BaseAddress = new System.Uri(baseUri) }; } public async Task<IEnumerable<Product>> GetAllProducts() { var response = await _client.GetAsync("/products"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<Product>>(content); } } public class Product { public int Id { get; set; } public string Name { get; set; } public double Price { get; set; } }
  31. 32 32 Test de Contratos (18/55) Hasta ahora que hemos

    visto: 1. Creación de un Controller. 2. Definición de la prueba de contrato: En ProductApiConsumerPactTests, configuramos una interacción esperada entre el consumidor y el proveedor, especificando el método HTTP, la ruta y la respuesta esperada. 3. Implementación del cliente: ProductApiClient es una clase que realiza una solicitud HTTP para obtener una lista de productos. Utiliza HttpClient para enviar solicitudes y JsonConvert para deserializar la respuesta JSON en objetos Product. 4. Verificación: La prueba verifica que el número de productos recibidos es el esperado y que todas las interacciones definidas en el contrato han sido ejecutadas correctamente. Ahora es el momento de lanzar el test: dotnet test Esto verificará que el consumidor cumple con las expectativas definidas en el contrato al interactuar con el proveedor simulado.
  32. 33 33 Test de Contratos (19/55) ¿Qué hemos logrado con

    esto? • Aislamiento: Permite probar el consumidor y el proveedor de manera aislada, asegurando que las interacciones esperadas son correctas. • Documentación: El contrato actúa como una documentación viva que describe cómo los servicios interactúan entre sí. • Colaboración: Facilita la colaboración entre equipos al compartir contratos que describen las expectativas mutuas. • Agilidad: Permite detectar cambios que rompen contratos rápidamente, antes de que se desplieguen en producción. Por supuesto esto es mejorable y debemos tener en cuenta: • Versionado de Contratos: A medida que los servicios evolucionan, es importante versionar los contratos para gestionar cambios y mantener la compatibilidad entre versiones. • Automatización en CI/CD: Integrar pruebas de contrato en tu pipeline de CI/CD puede ayudar a detectar problemas de integración tempranamente. El ejemplo completo lo podéis encontrar en: Ejemplo de Implementación (2ª Parte).
  33. 34 34 Test de Contratos (20/55) Pasos para Ejecutar Pruebas

    de Contratos 1. Ejecutar Prueba del Consumidor: Generar el Archivo de Contrato a) Objetivo: Verificar que el consumidor cumple con las interacciones esperadas y generar un archivo de contrato que describe estas interacciones. b) Acciones: • Corre las pruebas unitarias definidas en ProductApiConsumerPactTests. • Durante la ejecución de estas pruebas, PactNet capturará las interacciones entre el consumidor y el mock del proveedor. • Al finalizar, PactNet generará un archivo de contrato (generalmente en formato JSON) que detalla estas interacciones. • Resultado: Un archivo de contrato que refleja las expectativas del consumidor sobre cómo debería comportarse el proveedor.
  34. 35 35 Test de Contratos (21/55) 2. Compartir el Archivo

    de Contrato: Comunicación entre Equipos de Desarrollo a) Objetivo: Facilitar la colaboración entre los equipos de desarrollo del consumidor y del proveedor, asegurando que ambos estén alineados con respecto a las expectativas de interacción. b) Acciones: • El archivo de contrato generado se comparte con el equipo del proveedor. Esto puede hacerse manualmente o utilizando herramientas de integración continua como Pactflow, que facilita el intercambio de contratos. • Asegúrate de que ambos equipos revisen y acuerden las especificaciones del contrato. • Resultado: Ambos equipos tienen una comprensión clara y acordada de las interacciones esperadas entre los servicios.
  35. 36 36 Test de Contratos (22/55) 3. Ejecutar Prueba del

    Proveedor: Verificación de la Adherencia del Proveedor al Contrato a) Objetivo: Asegurar que el proveedor cumple con las expectativas definidas en el contrato y que puede proporcionar las respuestas esperadas al consumidor. b) Acciones: • El equipo del proveedor configura sus pruebas para "reproducir" las solicitudes desde el archivo de contrato hacia su API. • Utiliza PactNet para verificar que las respuestas del proveedor cumplen con las expectativas definidas por el consumidor. • Estas pruebas deben ejecutarse en un entorno donde el proveedor pueda ser probado de manera aislada, asegurando que todas las dependencias externas están simuladas o controladas. c) Resultado: Las pruebas del proveedor confirman que el servicio cumple con lo especificado en el contrato, proporcionando confianza de que las integraciones funcionarán en producción.
  36. 37 37 Test de Contratos (23/55) Antipatrón: Componentes de Contratos

    Crear un paquete NuGet que contenga los contratos de entrada y salida de un microservicio y compartirlo para que otros microservicios lo usen puede parecer una solución conveniente para garantizar que todos los servicios estén alineados respecto a las estructuras de datos. Sin embargo, esto introduce un antipatrón conocido como acoplamiento rígido. Contras • Acoplamiento Fuerte: Al compartir un paquete NuGet con contratos, todos los servicios que lo consumen dependen directamente de ese paquete. Cualquier cambio en el contrato obliga a una actualización de todos los servicios consumidores, lo que puede ser costoso y propenso a errores. • Dificultad en la Evolución: Evolucionar los contratos se vuelve complicado, ya que implica coordinar cambios a través de múltiples servicios y versiones de paquetes. Esto puede ralentizar el desarrollo e implementación de nuevas funcionalidades. Nota del autor En mis experiencias anteriores, me he enfrentado este antipatrón directamente, en situaciones donde no se había establecido un ADR para justificar esta práctica. Creo que al visibilizar este antipatrón contribuyó a tensiones dentro del equipo. Es importante evitar la idolatría de ciertas personas en un proyecto, ya que esto puede distorsionar la percepción de las decisiones. Como líder, siempre estoy abierto a que todos los miembros del equipo, desde juniors hasta seniors, tanto nuevos como veteranos, aporten sus ideas. Involucrar a todos en la toma de decisiones fortalece el compromiso y la cohesión del equipo. Sobre todo, cuando sabes que estas abogando por microservicios y concretamente por desacoplar y justamente introduces un acoplamiento rígido.
  37. 38 38 Test de Contratos (24/55) • Incompatibilidad de Lenguajes:

    Si los microservicios son poliglotas (es decir, escritos en diferentes lenguajes de programación), un paquete NuGet (que es específico para .NET) no podrá ser utilizado por servicios escritos en otros lenguajes como Python, Java, etc. • Falta de Flexibilidad: Los consumidores del contrato no tienen la libertad de adaptar el contrato a sus necesidades específicas. Esto puede llevar a una sobrecarga innecesaria en el código del consumidor para manejar casos que no son relevantes para ellos. Pros • Consistencia: Proporciona una forma centralizada de definir contratos, asegurando que todos los servicios utilicen las mismas estructuras de datos. • Facilidad de Uso: Para servicios que ya están en .NET, consumir un paquete NuGet es sencillo y bien integrado en el ecosistema.
  38. 39 39 Test de Contratos (25/55) Herramientas Herramienta Descripción Pros

    Contras PactFlow Plataforma de pruebas de contrato basada en el marco Pact y OpenAPI Specification, destacando por su solución de "pruebas de contrato bidireccionales". Fácil de usar, robusto, personalizable y escalable. Permite pruebas más útiles a escala. Requiere un conocimiento profundo de Pact, lo que podría suponer una curva de aprendizaje. HyperTest Centrado en pruebas de contratos de integración, ofrece pruebas y simulación a través de un servicio de auto-generación, diseñado para identificar problemas temprano en el desarrollo. Permite resolver problemas antes de la prueba en vivo o producción. Ofrece una funcionalidad amplia y poderosa. No tiene un historial tan largo en el mundo de las API, lo que implica una comunidad más pequeña. Spring Cloud Contract Ofrece pruebas de contratos a través de contratos impulsados por el consumidor, parte del poderoso marco Spring. Soporta múltiples paradigmas y es ideal si ya se usa Spring. Bloquea a los usuarios en Spring. Las pruebas deben ser escritas a mano, lo que puede ser costoso para APIs complejas. Dredd Permite compartir la descripción del API con la implementación backend del API. Soporta API Blueprint y OpenAPI 2 y 3 (experimental). Simple de usar, agnóstico al lenguaje para APIs simples. Limitado a Go, Node.js, Perl, PHP, Python, Ruby, y Rust. Se estira hasta sus límites con APIs grandes o complejas. Karate Marco de pruebas todo en uno con soporte para pruebas de rendimiento, UI y simulación. Diseñado como solución de bajo código. Ahorra tiempo al reutilizar pruebas, eficiente y rápido con hilos paralelos anidados. Puede limitar a los usuarios a un estilo específico que podría no ser adecuado para todos los casos. REST Assured Solución de pruebas API RESTful enfocada en Java, soporta JSON y XML y se integra bien con pipelines CI/CD. Potente y flexible con una comunidad fuerte. Específico para Java, con una curva de aprendizaje pronunciada. Microcks Aborda las pruebas de contrato con un paradigma de "simulación y prueba" utilizando estándares abiertos, desplegable en contenedores y Kubernetes. La simulación de extremo a extremo elimina conjeturas en las pruebas. Amplio soporte de protocolos e integraciones. Nuevo en la industria, con una comunidad más pequeña. Puede ser confuso para usuarios novatos debido a sus múltiples opciones de ejecución. Assertible Sistema tradicional de pruebas y monitoreo de servicios API en vivo, sincroniza con especificaciones y se integra con GitHub. Amplia gama de integraciones, fácil de usar para APIs no complejas. No tan completo como otras opciones, puede ser costoso para equipos pequeños. WireMock Enfoque pesado en simulación, excelente para probar escenarios de "peor caso", simulando dependencias rotas y APIs de terceros inestables. Ideal para escenarios de casos límite y dependencias rotas, mantiene la inercia del desarrollo. Puede ser excesivamente complejo y potente para casos simples, donde el valor agregado no sea evidente. Postman Soporte nativo para pruebas de contrato a través de plantillas de prueba de contrato. Popular herramienta de prueba de integración. Fácil de usar para usuarios de Postman, permite probar contratos de forma sencilla. No es una solución de prueba de contrato en forma completa, sirve más como un punto de partida.
  39. 40 40 Test de Contratos (26/55) Recomendación Personal: • Si

    buscas flexibilidad y soporte para múltiples lenguajes, Pact es una excelente elección. • Para proyectos basados en Spring, Spring Cloud Contract es la opción más integrada y eficiente. • Si tu equipo ya utiliza Postman como herramienta principal, puede ser un buen punto de partida para pruebas de contrato simples. Aquí he de mencionar la herramienta https://www.usebruno.com/, una alternativa open-Source integrada al 100% con Git. Herramienta Caso de Uso Ideal Comentario Pact Proyectos con múltiples lenguajes de programación y equipos distribuidos. Marco original de pruebas de contratos, altamente flexible. PactFlow Necesidad de pruebas bidireccionales y colaboración entre equipos. Ideal para grandes equipos y proyectos a escala. Spring Cloud Contract Proyectos basados en Spring Framework. Excelente para entornos Spring, pero limita a este ecosistema. Postman Proyectos pequeños o equipos con poca experiencia técnica en pruebas de contrato. Simple de usar, pero no tan completo como Pact. WireMock Simulación de dependencias externas y pruebas de casos límite. Ideal para entornos críticos, pero puede ser complejo. Microcks Integraciones con Kubernetes y protocolos abiertos. Recomendado para arquitecturas modernas basadas en contenedores. Dredd APIs simples con especificaciones OpenAPI. Excelente para validar contratos con OpenAPI. Nota del autor Integrar pruebas de contratos en un pipeline de CI/CD es esencial para garantizar la compatibilidad continua entre consumidores y proveedores. Pact se integra fácilmente en pipelines mediante herramientas como Pact Broker o PactFlow, lo que permite publicar, verificar y versionar contratos de manera automatizada. Esto asegura que cualquier cambio en un servicio sea validado antes de llegar a producción. Por otro lado, Bruno, una herramienta ligera y orientada a pruebas de API, puede complementar este flujo al validar contratos directamente desde especificaciones OpenAPI durante las etapas iniciales del desarrollo. Por tanto las herramientas son inclusivas y no exclusivas
  40. 41 41 Test de Contratos (27/55) Pact es considerado el

    marco original para pruebas de contratos, con soporte para múltiples lenguajes y una comunidad activa. Ofrece funcionalidades como el servidor de simulación de consumidores, coincidencia de tipos de campos y generación de contratos. Ventajas de Pact • Amplio soporte para diferentes lenguajes. • Comunidad activa y extensa. • Respaldo de SmartBear. Desventajas de Pact • No soporta todos los protocolos y transportes nativamente, como gRPC. PactFlow • PactFlow ofrece una implementación SaaS del Broker de Pact con características adicionales como soporte para pruebas de contrato bidireccionales.
  41. 42 42 Test de Contratos (28/55) DIY (Do It Yourselft)

    Paso 1: Generar el Contrato del Consumidor con Esquema using System.IO; using System.Net.Http; using System.Threading.Tasks; using Xunit; using Newtonsoft.Json.Linq; public class ConsumerContractTests { private readonly HttpClient _client; public ConsumerContractTests() { _client = new HttpClient(); _client.BaseAddress = new Uri("http://mock-api-url.com"); } [Fact] public async Task GenerateConsumerContractWithSchema() { var response = await _client.GetAsync("/recurso"); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync();
  42. 43 43 Test de Contratos (29/55) // Define el contrato

    y el esquema esperado var contract = new JObject { { "endpoint", "/recurso" }, { "method", "GET" }, { "expectedResponseContains", "campoEsperado" }, { "schema", new JObject { { "type", "object" }, { "properties", new JObject { { "campoEsperado", new JObject { { "type", "string" } } } } }, { "required", new JArray { "campoEsperado" } } } } }; // Guardar el contrato en un archivo JSON File.WriteAllText("ConsumerContract.json", contract.ToString()); } } Mi recomendación es crear un proyecto llamado [tu_ejemplo].ConsumerContractTest.csproj, de esta forma podrás integrarlo mejor en el flujo y ejecutarlo cuando lo necesites. Además, es conveniente guardar este contrato en un Azure Storage Account o bien enviarlo a Apicurio ya sea exportando toda la definición de OpenAPI.
  43. 44 44 Test de Contratos (30/55) Paso 2: Validar el

    Contrato y Esquema en el Proveedor using System.IO; using System.Net.Http; using System.Threading.Tasks; using Xunit; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; public class ProviderContractTests { private readonly HttpClient _client; public ProviderContractTests() { _client = new HttpClient(); _client.BaseAddress = new Uri("http://real-api-url.com"); } [Fact] public async Task ValidateProviderAgainstConsumerContractWithSchema() { // Cargar el contrato del consumidor var contractContent = File.ReadAllText("ConsumerContract.json"); var contract = JObject.Parse(contractContent); var endpoint = contract["endpoint"].ToString(); var expectedResponseContains = contract["expectedResponseContains"].ToString(); var schema = contract["schema"].ToString();
  44. 45 45 Test de Contratos (31/55) // Realizar la solicitud

    a la API del proveedor var response = await _client.GetAsync(endpoint); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); var jsonResponse = JObject.Parse(content); // Validar que la respuesta cumple con las expectativas del contenido Assert.Contains(expectedResponseContains, content); // Validar que la respuesta cumple con el esquema JSON var jsonSchema = JSchema.Parse(schema); IList<string> validationErrors = new List<string>(); bool isValid = jsonResponse.IsValid(jsonSchema, out validationErrors); Assert.True(isValid, $"La respuesta no cumple con el esquema JSON. Errores: {string.Join(", ", validationErrors)}"); } } Siguiendo con la recomendación anterior este caso sería [tu_ejemplo].ProviderContractTest.csproj, leyendo el JSON desde Azure Storage Account o bien de Apicurio. De esta forma podrás evitar tener herramientas de terceros o como mucho Apicurio que nos sirve no solo para lo que os muestro en este artículo. , si no que nos ayudará en nuestros microservicios en muchas más cosas. También puedes usar Azure API Management para desplegar esos contratos con mucho cuidado por que aquí podrías estar rompiendo tu entorno de Dev, y tienes que pensar bien la estrategia de despliegue para no afectar a tus compañeros.
  45. 46 46 Test de Contratos (32/55) Paso 3: Integración en

    el Proceso de Build • Generación del Contrato: Asegúrate de que las pruebas del consumidor que generan el contrato se ejecuten como parte del proceso de build. Puedes usar scripts de construcción (como MSBuild, scripts de shell, etc.) para ejecutar estas pruebas y generar el archivo JSON. • Distribución del Contrato: Una vez generado, el archivo JSON debe ser accesible para el proveedor. Esto puede ser a través de un sistema de control de versiones compartido o mediante un artefacto de build. • Validación Continua: Incluye las pruebas del proveedor en tu pipeline de CI/CD para validar continuamente que la API cumple con los contratos generados por los consumidores. Este enfoque asegura que no solo las expectativas del consumidor se cumplan, sino también que la estructura de las respuestas de la API sea la correcta. Al usar esquemas JSON, puedes detectar errores de estructura en las respuestas antes de que causen problemas en producción. Importante Cuando trabajas con proyecciones JSON, es común que ciertos campos no se incluyan en la salida JSON si están vacíos, especialmente si la serialización está configurada para omitir valores nulos o vacíos. Esto puede ser problemático al realizar pruebas de contrato, ya que los consumidores esperan un conjunto completo de campos en la respuesta.
  46. 47 47 Test de Contratos (33/55) Para asegurarte de que

    tus pruebas de contrato sean precisas y completas, considera los siguientes enfoques: 1. Configuración de Serialización: Asegúrate de configurar el serializador JSON en tus pruebas para incluir todos los campos, incluso si están vacíos o nulos. Si estás utilizando Json.NET, puedes configurar el serializador de la siguiente manera: var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include }; var jsonResponse = JsonConvert.DeserializeObject<JObject>(content, settings); 2. Validación del Esquema: Cuando valides el esquema JSON, asegúrate de que el esquema esté configurado para requerir todos los campos que esperas, independientemente de si están vacíos. Esto se puede hacer especificando los campos requeridos en el esquema JSON: { "type": "object", "properties": { "campoEsperado": { "type": "string" }, "otroCampo": { "type": "integer" } }, "required": ["campoEsperado", "otroCampo"] }
  47. 48 48 Test de Contratos (34/55) 3. Pruebas de Contrato:

    En las pruebas de contrato, asegúrate de que los datos de prueba que utilizas para generar respuestas de ejemplo contengan todos los campos requeridos, incluso si algunos de ellos deben ser valores vacíos o nulos. 4. Documentación y Comunicación: Es crucial que todos los equipos involucrados comprendan cómo se manejan los valores nulos y vacíos en las respuestas JSON. Esto debe estar claramente documentado en las especificaciones del contrato para evitar malentendidos.
  48. 49 49 Test de Contratos (35/55) Flujo de trabajo con

    Apicurio En el siguiente artículo detallo paso a paso como ser trabaja con Apicurio: Acelerando el Desarrollo de Software con Apicurio y Kiota de Microsoft. En esta sección voy a centrarme en como Apicurio puede ser un aliado para facilitar y automatizar el flujo de pruebas de contratos, desde el diseño inicial hasta su validación en entornos de pruebas. 1. Diseño de la API con Apicurio El proceso comienza con la definición de la API en Apicurio, utilizando el editor visual para crear o importar especificaciones basadas en OpenAPI. Esto permite a los equipos trabajar de manera colaborativa en la definición de los contratos. Pasos: • Crear o Importar la Especificación: • Si ya existe una especificación, puedes importarla directamente en Apicurio. Si no, utiliza el editor para definir los endpoints, métodos HTTP, parámetros y esquemas de respuesta. • Asegúrate de incluir ejemplos detallados de los cuerpos de solicitud y respuesta en el esquema para facilitar pruebas precisas.
  49. 50 50 Test de Contratos (36/55) • Versionado de Contratos:

    • Utiliza el sistema de versionado integrado de Apicurio para mantener un historial de cambios en la especificación. Esto es crucial para garantizar la compatibilidad entre versiones y realizar pruebas en paralelo para consumidores y proveedores que utilicen diferentes versiones de la API. • Publicación: • Una vez definido el contrato, publícalo en el repositorio central de Apicurio, donde todos los equipos puedan acceder y colaborar. 2. Generación Automática de Código Apicurio simplifica la implementación al generar automáticamente código base para consumidores y proveedores. Esto asegura que ambos lados cumplan con el contrato establecido desde el inicio del desarrollo. Pasos: • Descarga de SDKs y Plantillas: • Apicurio permite generar SDKs en varios lenguajes (Java, Python, C#, etc.) basados en la especificación OpenAPI. Esto acelera el desarrollo y reduce errores en la implementación del contrato. • Integración con Herramientas de Pruebas: • Utiliza los contratos generados para configurar herramientas como Pact, Dredd o Postman, asegurando que las pruebas de contrato estén alineadas con la especificación publicada en Apicurio.
  50. 51 51 Test de Contratos (37/55) 4. Gestión de Cambios

    y Compatibilidad Apicurio facilita la gestión de cambios en contratos, asegurando que las actualizaciones en las especificaciones no rompan las integraciones existentes. Esto es especialmente importante en arquitecturas de microservicios, donde múltiples consumidores dependen de un proveedor común. Pasos: • Versionado de Especificaciones: • Utiliza el sistema de versionado de Apicurio para crear nuevas versiones de la especificación cada vez que se realizan cambios. Esto permite a los equipos trabajar con versiones específicas mientras las actualizaciones se implementan gradualmente. • Por ejemplo, si se agrega un nuevo endpoint o se modifica un esquema de respuesta, los consumidores pueden continuar utilizando la versión anterior mientras adaptan sus servicios a la nueva especificación. • Documentación Clara de Cambios: • Apicurio permite agregar notas detalladas sobre los cambios realizados en cada versión. Esto asegura que todos los equipos comprendan cómo afectan las modificaciones a los consumidores y proveedores. • Incluye detalles como campos nuevos, campos obsoletos, cambios en parámetros y tipos de datos.
  51. 52 52 Test de Contratos (38/55) • Pruebas de Compatibilidad:

    • Configura pruebas en el pipeline de CI/CD para validar la compatibilidad entre versiones. Por ejemplo: • Verifica que la nueva versión del proveedor sigue siendo compatible con los contratos de consumidores que utilizan versiones anteriores. • Realiza pruebas de regresión para asegurarte de que las funcionalidades existentes no se vean afectadas. • Migración Gradual: • Planea una migración gradual para los consumidores y proveedores hacia la nueva versión del contrato. Esto puede incluir un periodo de coexistencia en el que ambas versiones estén activas, permitiendo una transición sin interrupciones. 5. Monitoreo y Feedback Continuo Apicurio no solo ayuda en la definición y validación de contratos, sino que también puede integrarse con herramientas de monitoreo para asegurar que las APIs cumplen con las especificaciones en tiempo real.
  52. 53 53 Test de Contratos (39/55) Pasos: • Integración con

    Sistemas de Monitoreo: • Utiliza herramientas como Azure API Management, Prometheus, o Grafana para monitorear las interacciones entre servicios y detectar discrepancias con el contrato especificado. • Por ejemplo, si un proveedor comienza a devolver respuestas que no cumplen con el esquema definido en Apicurio, el sistema de monitoreo puede generar alertas automáticas. • Feedback a Equipos: • Configura un flujo de retroalimentación para informar a los equipos de desarrollo sobre problemas detectados en producción. Esto puede incluir: • Alertas en tiempo real sobre fallos de contrato. • Informes periódicos sobre el cumplimiento de especificaciones.
  53. 54 54 Test de Contratos (40/55) Beneficios del Flujo de

    Trabajo con Apicurio • Centralización y Colaboración: Todos los equipos tienen acceso a una fuente única y centralizada de verdad sobre las especificaciones de la API, fomentando la colaboración y reduciendo malentendidos. • Automatización y Eficiencia: La integración con pipelines de CI/CD y herramientas de pruebas automatizadas reduce la carga manual y aumenta la velocidad de desarrollo y despliegue. • Gestión de Cambios: La capacidad de versionar contratos y gestionar cambios asegura que los servicios puedan evolucionar sin comprometer la estabilidad de las integraciones existentes. • Reducción de Riesgos: Al detectar problemas en etapas tempranas del desarrollo y validar automáticamente los contratos, se minimizan los riesgos de fallos en producción. El primer punto Centralización y Colaboración, es lo que más fuerte hace a este sistema.
  54. 55 55 Test de Contratos (41/55) Flujo de trabajo con

    Azure API Management Azure API Management (APIM) es una plataforma de gestión de APIs que facilita la publicación, seguridad, monitoreo y análisis de servicios. Además de estas capacidades, APIM puede desempeñar un papel clave en las pruebas de contratos al proporcionar herramientas para simular, validar y monitorear el cumplimiento de las especificaciones de API. A continuación, se describe un flujo de trabajo para realizar pruebas de contratos utilizando Azure API Management. El flujo completo sería el siguiente: 1. Diseño: El equipo define el contrato de la API en OpenAPI y lo publica en Azure API Management. 2. Simulación: APIM se utiliza para simular interacciones entre consumidores y proveedores en etapas iniciales de desarrollo. 3. Validación: Se configuran pruebas automatizadas en el pipeline CI/CD para validar que tanto consumidores como proveedores cumplen con el contrato. 4. Monitorización: APIM supervisa las interacciones en producción para garantizar el cumplimiento continuo del contrato. 5. Feedback: Los resultados de las pruebas y monitoreo se comparten con los equipos para mejorar continuamente la calidad de la API.
  55. 56 56 Test de Contratos (42/55) Implementar pruebas de contratos

    utilizando Azure API Management ofrece múltiples ventajas que mejoran tanto la calidad como la confiabilidad de las integraciones entre servicios: • Centralización de la Gestión de APIs: Azure API Management actúa como un punto único para diseñar, publicar, simular, y monitorear APIs, facilitando la colaboración entre equipos de desarrollo, pruebas y operaciones. • Validación Automatizada: Las políticas de validación de solicitudes y respuestas garantizan que las interacciones cumplan con los contratos definidos, reduciendo el riesgo de errores humanos. • Simulación de APIs (Mocking): Permite simular el comportamiento del proveedor incluso antes de que esté implementado, lo que acelera el desarrollo del consumidor y fomenta el trabajo paralelo. • Integración con CI/CD: Facilita la incorporación de pruebas de contrato en los pipelines de CI/CD, asegurando que cualquier cambio en la API sea validado automáticamente antes de ser desplegado a producción. • Monitoreo en Tiempo Real: Las capacidades de monitoreo de APIM permiten detectar y resolver problemas de incumplimiento de contratos en producción de manera proactiva. • Soporte para Versionado: Azure API Management permite gestionar múltiples versiones de una API, asegurando que las integraciones con consumidores que utilizan versiones anteriores no se rompan durante las actualizaciones. • Retroalimentación Continua: Al monitorear y reportar problemas automáticamente, APIM ayuda a los equipos a identificar áreas de mejora en sus APIs y a implementar soluciones rápidamente. Desde mi punto de vista si ya tienes un API Gateway puedes extender este para usarlo en las pruebas de contratos, pero si vas a montar uno para este trabajo, creo que es sobre-arquitecturizar la aplicación.
  56. 57 57 Test de Contratos (43/55) Ejemplo de Implementación (2ª

    Parte) con Pact DIY Cuando comencé a preparar este documento, mi intención inicial era utilizar Pact como la opción principal. Sin embargo, considerando que algunos proyectos open-source están adoptando modelos de pago, decidí que no era apropiado mostrar Pact como primera elección. Por eso, he optado por comenzar esta demostración con una implementación manual (DIY). Creo sinceramente que este enfoque ofrece un mayor valor formativo, ya que permite entender desde cero cómo funcionan los tests de contrato sin depender de capas de abstracción externas. Además, estoy convencido de que la llamada "1ª Parte" de este contenido ya proporciona los fundamentos suficientes para que seas capaz de replicar todo el proceso por ti mismo, sin necesidad de herramientas específicas como Pact. Y si en el futuro decides utilizar Pact, lo harás con un conocimiento profundo de su base conceptual y entenderás mejor qué problemas está resolviendo realmente. Antes de continuar, me gustaría aclarar que he evitado intencionadamente el uso de fixtures que levantan servidores automáticamente. En esta demo podrás ejecutar todo de forma manual para asentar mejor los conocimientos. Más adelante, en un entorno de CI/CD, sí podremos apoyarnos en fixtures automatizados para validar sin tener servidores desplegados —de lo contrario, estaríamos más cerca de un test de integración que de un test de contrato puro.
  57. 59 59 Test de Contratos (45/55) Proceso explicado paso a

    paso: 1. Inicio del test • El test arranca en xUnit y representa un test de verificación del proveedor. • Carga el contrato desde el archivo local ProductContract.json. 2. Carga del contrato • El test extrae del contrato • La ruta (/products) • El método (GET) • El esquema JSON esperado (estructura, tipos, campos requeridos) Actor Rol Contrato (JSON) Contiene el contrato que especifica el endpoint, método y esquema de la respuesta HttpClient Cliente HTTP que lanza una petición real a la API API Provider La API real, escrita en Minimal API o Web API en .NET JsonSchemaValidator Código personalizado que valida la respuesta JSON contra el contrato Test Test automatizado (xUnit) que orquesta todo el flujo de verificación DIY
  58. 60 60 Test de Contratos (46/55) 4. Petición a la

    API • El HttpClient hace una llamada real al proveedor (GET /products). • La API responde con un JSON, como por ejemplo:json [ { "id": 1, "name": "Laptop", "price": 999.99 }, { "id": 2, "name": "Smartphone", "price": 499.99 } ] 5. Validación del contenido • El JsonSchemaValidator • Recorre cada objeto del array • Valida que cada propiedad requerida existe y su tipo es correcto • "Id" debe existir y ser integer • "Name" debe ser string • "Price" debe ser number 6. 5. Resultado del test • Si todo el JSON cumple el contrato: El test pasa • Si falta una propiedad o un tipo es incorrecto: Se lanza una excepción y el test falla El ejemplo en GitHub.
  59. 61 61 Test de Contratos (47/55) Ejemplo de Implementación (2ª

    Parte) con Pact Después de haber abordado en profundidad el enfoque DIY (Do It Yourself) para validar contratos de manera manual, es el momento de mostrar cómo se realiza este proceso con una herramienta diseñada específicamente para ello: Pact. Si bien en un inicio opté por evitar dependencias externas para reforzar el aprendizaje conceptual, creo que es igualmente valioso mostrar cómo se puede automatizar gran parte del flujo gracias a PactNet. De esta forma, quien haya seguido todo el recorrido desde el principio, podrá apreciar claramente qué simplifica Pact, qué abstrae y dónde aplica realmente su valor. ¿Qué se busca esta implementación con Pact? • Mostrar la generación automática del contrato a partir del test del consumidor. • Simular el proveedor mediante un servidor HTTP mock (Mock Provider). • Verificar que el proveedor real responde según lo definido por el consumidor. • Comparar este enfoque con el flujo manual anterior para consolidar el conocimiento.
  60. 63 63 Test de Contratos (49/55) Proceso explicado paso a

    paso: Fase 1: 1. Generación del contrato 2. El test del consumidor (TestConsumer) define qué espera recibir al llamar al endpoint /products. 3. Pact genera un mock (API_Mock) que responde según esa expectativa. 4. El test lanza una llamada real al mock y valida que el objeto simulado es correcto.Si todo es correcto, se genera automáticamente el fichero pact en formato JSON (Product API Consumer - Product API Provider.json). Este contrato actúa como un acuerdo firmado entre consumidor y proveedor. Actor Rol TestConsumer Test del consumidor que define expectativas API_Mock Servidor simulado que responde como si fuera el proveedor real PactFile Fichero .json generado con la definición del contrato TestProvider Test del proveedor que verifica si la API real cumple el contrato RealAPI Tu API ejecutándose localmente en un puerto específico
  61. 64 64 Test de Contratos (50/55) Fase 2: 1. El

    test del proveedor (TestProvider) lee el contrato pactado. 2. Lanza una petición real al API (RealAPI) y compara la respuesta con lo que indica el contrato. 3. Si la estructura, el status y el body coinciden: el test pasa. 4. Si hay diferencias: el test falla e informa del motivo exacto. ¿Por qué es importante esto? Gracias a este flujo: • El consumidor y el proveedor evolucionan de forma independiente. • Se detectan incompatibilidades en integración antes del despliegue. • La validación es completamente automática en pipelines CI/CD. Como se puede observar, esto es exactamente lo mismo que hemos realizado en el proceso DIY, por tanto, en este punto debes analizar si compensa o no asociarse a una librería que de un momento a otro puede convertirse en un modelo de pago. El ejemplo en GitHub.
  62. 65 65 Test de Contratos (51/55) ¿Convertir Pruebas de Integración

    en Pruebas de Contrato? (Contratos vs Integración) Uno de los grandes debates al trabajar con microservicios es: ¿Necesito tantas pruebas de integración entre servicios o puedo cubrirlas con pruebas de contrato más eficientes? Las pruebas de integración (levantar consumidor y proveedor reales – ¡REALES!, con base de datos y entorno conectado) son costosas, frágiles y difíciles de mantener. En cambio, las pruebas de contrato permiten verificar que ambos extremos siguen hablando el mismo idioma, sin levantar ambos a la vez. Escenario tradicional (prueba de integración): • Servicio A llama a B (GET /clientes/123) • Test levanta ambos y verifica que A recibe la respuesta esperada Alternativa con pruebas de contrato: • Test del consumidor (A): simula B con un mock (por ejemplo, PactNet mock server o TestServer personalizado) y define un contrato que espera { "id": 123, "nombre": “Jose María" } • Test del proveedor (B): se levanta el servicio real, y el contrato generado por A se valida contra su respuesta. Resultado: se garantiza que, si B cambia algo, romperá el contrato. No se necesita levantar A y B simultáneamente.
  63. 66 66 Test de Contratos (52/55) En los ejemplos de

    GitHub anteriores ya habéis visto como funcionan a nivel de código; pero lo que no habéis visto es:¿Qué gano al dejar la integración real? Casos donde no basta con pruebas de contrato Aunque contract testing puede cubrir la mayoría de las interacciones, hay situaciones donde mantener algunas pruebas de integración es necesario: • Validar autenticación real entre servicios (JWT, Azure AD, etc.) • Comprobar configuración de red o DNS interna (p. ej., http://productos-internal) • Confirmar que los tiempos de respuesta combinados cumplen SLAs • Ejecución de flujos E2E críticos (checkout completo, creación de pedido, etc.) Recomendación: mantén 1-2 pruebas integrales E2E por flujo crítico, pero desplaza el resto a contrato + componente. Criterio Prueba de Integración tradicional Prueba de Contrato (Pact o DIY) Tiempo de ejecución Alto (requiere ambos servicios) Bajo (uno por vez, sin dependencias) Diagnóstico de fallos Difuso Preciso (fallo de A o B) Coste de mantenimiento Alto Medio-bajo Escalabilidad (CI/CD) Difícil Fácil Verificación de compatibilidad Limitada Totalmente aislada
  64. 67 67 Test de Contratos (53/55) Estrategias de Versionado de

    Contratos y Gestión del Cambio Cuando trabajamos con microservicios, los contratos entre consumidor y proveedor evolucionan constantemente. Ya sea agregando campos, modificando respuestas o reestructurando recursos, cada cambio puede romper a un consumidor si no se gestiona adecuadamente. Aquí es donde el versionado de contratos y su correcta gobernanza se vuelven fundamentales. El versionado de contratos en arquitecturas de microservicios no se limita a añadir un /v2 en la URL. Supone establecer una estrategia integral de evolución del API y sus contratos, asegurando compatibilidad, trazabilidad y coordinación entre equipos. En este marco: • Compatibilidad hacia atrás como regla base: Las adiciones deben ser toleradas por consumidores anteriores (fields opcionales, estructuras extensibles). Cualquier breaking change debe ser deliberado, justificado y acompañado de una estrategia de coexistencia. • Versionado Semántico + PactBroker u OpenAPI: Utiliza versiones etiquetadas y trazables por consumidores y proveedores. Versiona también el contrato como artefacto (no solo el endpoint), y automatiza las matrices de compatibilidad. • Múltiples versiones vivas: Soporta ejecución simultánea de contratos v1, v2... en CI/CD. Esto permite verificar la compatibilidad sin comprometer la estabilidad productiva.
  65. 68 68 Test de Contratos (54/55) • Gobernanza del cambio:

    Cada cambio relevante en el contrato debe tener justificación, ticket o changelog asociado. Comunica proactivamente a los consumidores, y ofrece herramientas para que puedan adaptar sus pruebas antes de la implementación final. • Monitoreo de uso en producción: Antes de retirar una versión antigua, asegúrate con métricas (por ejemplo, en Azure API Management) de que ya no tiene tráfico relevante. Sin esta información, podrías romper dependencias invisibles. • Versión ≠ rama: Evita ramificar microservicios por versión. El contrato puede cambiar, pero el proveedor debe mantener backward compatibility en el mismo código base cuando sea posible (técnicas como feature toggles, DTOs condicionales o discriminated unions ayudan). Por esas razones y algunas más que me habré dejado en el tintero se debe implementar La estrategia de Consumer-Driven Contracts (CDC) un enfoque moderno y altamente eficaz para pruebas de contrato en arquitecturas distribuidas, especialmente en microservicios. Aquí te lo explico con un nivel técnico adecuado para alguien con experiencia en diseño de APIs y pruebas automatizadas. Ya lo hemos estado viendo antes, pero para terminar quería darte el nombre formal de esta estrategia y así usamos estas últimas líneas para hacer un resumen de este proceso: ¿Provocarías un incendio en tu casa solo para comprobar que funciona el detector de humo? No. Pulsas el botón de prueba.
  66. 69 69 Test de Contratos (55/55) Es un enfoque en

    el que los consumidores de una API (u otro servicio) son quienes definen y controlan el contrato esperado con el proveedor. En lugar de que el proveedor documente lo que expone y asuma que los consumidores se adaptarán, cada consumidor describe lo que necesita del proveedor, y esos requerimientos se formalizan en un contrato verificable. Estos contratos: • Describen la interacción esperada (método, ruta, headers, cuerpo de la respuesta, etc.). • Se generan en los tests del consumidor (por ejemplo, usando Pact). • Son verificados por el proveedor, que debe garantizar que cumple con las expectativas definidas por los consumidores. En la práctica el funcionamiento es: 1. Test del consumidor genera el contrato • El consumidor escribe un test que simula cómo va a usar la API (ej: GET /productos/42 devuelve { id, name, price }). • Este test se ejecuta contra un servidor mock (simulado) y al pasar, genera un contrato JSON (por ejemplo, un fichero Pact). 2. El contrato se publica (normalmente en un Pact Broker): Este broker actúa como repositorio central de contratos y facilita la colaboración entre equipos. 3. El proveedor descarga el contrato y lo verifica • El proveedor ejecuta tests que arrancan su API real (en modo test) y comprueban si cumple lo que el contrato espera. • Si rompe alguna expectativa del consumidor, el test falla.
  67. 70 70 Test de Componentes (1/8) Introducción Las pruebas de

    componentes se sitúan en un nivel intermedio entre las pruebas unitarias y las de integración completa. En el contexto de microservicios, consisten en ejecutar un único servicio en un entorno controlado, validando su comportamiento funcional (endpoints, lógica de negocio, acceso a datos, etc.) sin desplegar todo el sistema. A diferencia de los test unitarios, aquí el servicio se levanta como una “caja negra” e interactúa con stubs para sus dependencias externas; a diferencia de las pruebas de integración totales, no se requieren todos los servicios del ecosistema, sino sólo los necesarios simulados o en memoria. Este enfoque permite verificar a fondo la lógica interna del microservicio (incluyendo base de datos en memoria o servicios internos) mientras se evitan dependencias reales externas que compliquen el entorno de prueba. Nota del autor Un stub (fake, mock, spy, dummy) es una implementación simulada o "falsa" de una dependencia que se usa en pruebas para controlar el comportamiento externo del sistema bajo prueba. En lugar de invocar a un componente real (como una base de datos, un API externa o un servicio de mensajería), se sustituye por un stub que responde de forma predecible y controlada. ¿Para qué se usan? En pruebas de microservicios (especialmente pruebas de componentes o de contrato), los stubs permiten: • Aislar el componente bajo prueba. • Simular respuestas válidas o erróneas de servicios externos. • Evitar efectos secundarios (ej. llamadas reales a terceros, escritura en bases de datos). • Reproducir escenarios de prueba difíciles o costosos de provocar en un entorno real.
  68. 71 71 Test de Componentes (2/8) ¿Qué son las Pruebas

    de Componentes? Las pruebas de componentes de microservicios validan un servicio aislado como si fuese un cliente externo llamando a sus interfaces (por ejemplo, peticiones HTTP REST). Se enfocan en ejecutar el microservicio como caja negra, verificando que responda correctamente a diferentes solicitudes de entrada. En la práctica, se utiliza un servidor de prueba en memoria (por ejemplo, con WebApplicationFactory<T>) para levantar el servicio completo, inyectando fakes de sus dependencias. • Se evalúa la funcionalidad real del servicio (incluyendo rutas, controladores, filtros y lógica integrada) sin desplegar otros microservicios reales. • Las dependencias externas (otras APIs, colas, etc.) se sustituyen por stubs o mocks controlados. Así, la prueba puede simular respuestas positivas y negativas de servicios dependientes y verificar cómo reacciona el componente ante esas condiciones. • En ASP.NET Core es común usar WebApplicationFactory<Startup> o WebApplicationFactory<Program> para crear un servidor de prueba en memoria. El framework levanta la aplicación completa (o la sección mínima necesaria) y permite usar HttpClient para invocar los endpoints del microservicio. En los tests de componente se suele sobreescribir la configuración o inyectar servicios de prueba mediante ConfigureWebHost (o ConfigureTestServices) para reemplazar implementaciones reales con versiones simuladas. Las pruebas de componentes actúan como clientes automatizados que envían diferentes peticiones al microservicio y verifican sus respuestas, centrándose en el cumplimiento de los requisitos del servicio.
  69. 72 72 Test de Componentes (3/8) Importancia de las Pruebas

    de Componentes Las pruebas de componentes aportan beneficios clave al ciclo de desarrollo de microservicios: • Detección temprana de fallos complejos: Validan flujos de negocio completos dentro del servicio (incluyendo acceso a base de datos, validaciones, mapeos, etc.) que los test unitarios podrían pasar por alto. Por ejemplo, errores de mapeo de entidades a DTO, mal manejo de transacciones o configuraciones incorrectas del servicio. • Aislamiento de dependencias externas: Al simular servicios externos, se evitan falsos negativos por fallos o indisponibilidades de terceros. Esto da mayor confiabilidad en cada build o PR, ya que los tests no dependen de entornos externos. • Rapidez y estabilidad de pruebas: En comparación con las pruebas de integración completas, los tests de componentes son más rápidos de ejecutar porque evitan levantar todo el ecosistema. Los resultados son más predecibles, lo que permite detectarlos como parte de la integración continua sin largas esperas. • Facilitan el desarrollo independiente: Permiten que cada equipo de servicio verifique su propio servicio en condiciones controladas. Al garantizar localmente que el servicio cumple su contrato funcional interno, se acelera la entrega. Se complementan bien con pruebas de contratos y aseguran que los cambios internos no rompan la funcionalidad esperada. Las pruebas de componentes brindan confianza adicional en la calidad del servicio antes de hacer pruebas integrales o desplegar en entornos compartidos. Ayudan a reducir el riesgo de introducir errores en producción y mejoran la agilidad del equipo al detectar problemas en el mismo nivel de servicio (donde son más fáciles de corregir).
  70. 73 73 Test de Componentes (4/8) Desafíos de las Pruebas

    de Componentes Implementar pruebas de componentes en microservicios implica algunos retos a considerar: • Aislar dependencias externas: Muchos microservicios consumen otros servicios, bases de datos, colas o APIs de terceros. Probar un servicio en aislamiento puede requerir simular decenas de dependencias. En la práctica se recurre a virtualización de servicios o dobles de prueba para simular esas APIs externases.parasoft.com. Aunque esta simulación simplifica el entorno, requiere mantener los stubs actualizados con el comportamiento esperado, lo que añade carga de mantenimiento. • Configuración de entorno de pruebas: Es necesario replicar configuraciones de ambiente (como cadenas de conexión, variables, certificados) en versiones de prueba. El uso de bases de datos en memoria (p. ej. UseInMemoryDatabase en Entity Framework) o servidores de mensajería simulados ayuda, pero el test debe inicializar correctamente los datos iniciales de prueba. Un mal manejo puede llevar a resultados inconsistentes o pruebas flaqueantes. • Mantenimiento de fakes y mock setups: A medida que el microservicio evoluciona, los mocks/stubs deben ajustarse. Por ejemplo, si cambia la interfaz de un servicio dependiente, hay que actualizar el stub correspondiente. Esto puede generar deuda técnica si no se sincronizan adecuadamente con las pruebas de contrato que formalizan esos cambios. • Costo de ejecución: Aunque más livianas que una integración completa, las pruebas de componentes aún toman más tiempo que un test unitario. Requieren inicializar el servidor de prueba y cargar parte del contexto de la aplicación. No conviene abusar de ellas para casos triviales, sino enfocarse en escenarios clave de la lógica del servicio.
  71. 74 74 Test de Componentes (5/8) • Cobertura limitada de

    entorno real: Al sustituir las dependencias reales, ciertos problemas pueden no detectarse hasta la integración final (por ejemplo, problemas de red o configuración de infraestructuras reales). Es vital complementar los tests de componentes con pruebas de integración y contrato para cubrir la comunicación entre servicios en entornos reales. Las pruebas de componentes ayudan a simplificar el entorno de prueba de un microservicio aislado, pero requieren un esfuerzo inicial para simular sus dependencias y un mantenimiento continuo de esos fakes de prueba.
  72. 75 75 Test de Componentes (6/8) Ejemplo de Implementación en

    .NET Este ejemplo es intencionadamente trivial y está dirigido a quienes ya están familiarizados con el ecosistema de pruebas en .NET. Utiliza xUnit, WebApplicationFactory y HttpClient para ejecutar una prueba real contra un endpoint expuesto por una API ejecutándose en memoria. No entraré en más detalle sobre cómo funcionan estas herramientas ni en los fundamentos de xUnit, TestServer, o los asserts, ya que no es el propósito de este libro enseñar testing desde cero. El foco está en mostrar cómo encajan estas pruebas dentro de una estrategia de validación en arquitecturas distribuidas. Si estás cómodo escribiendo pruebas unitarias o de integración en .NET, el código es autoexplicativo. Si no, te recomiendo revisar la documentación oficial de Microsoft sobre pruebas en ASP.NET Core antes de continuar. Ejemplo en GitHub.
  73. 76 76 Test de Componentes (7/8) ¿Cómo se integran en

    el flujo de trabajo? Las pruebas de componentes encajan en el ciclo de desarrollo y CI/CD de la siguiente forma práctica: • En desarrollo local: Los desarrolladores ejecutan estos tests junto con los unitarios para validar rápidamente la lógica del servicio. Al ser relativamente rápidos (no requieren todo el sistema), se suelen ejecutar en Visual Studio o con dotnet test al compilar el proyecto. • En la integración continua: Se configuran como una etapa fija del pipeline. Por ejemplo, tras el build y las pruebas unitarias, se corre el conjunto de tests de componentes de cada microservicio. Si fallan, el pipeline se detiene antes de avanzar a etapas posteriores (contratos, integración completa, despliegue). Esto garantiza que cada servicio cumple sus requisitos internos antes de integrar múltiples servicios. • Junto a pruebas de contrato: Las pruebas de componentes validan el propio servicio, mientras que las pruebas de contrato se centran en los acuerdos entre servicios. En la práctica, primero un equipo puede usar test de componente para su propio servicio; luego, como parte del flujo de contrato (p. ej. con Pact o Apicurio), se valida que la interfaz del servicio no rompa a sus consumidores. Ambas prácticas son complementarias y pueden automatizarse de forma encadenada. • Entrega continua y QA: Al desplegar en entornos de QA o staging, los tests de componentes pueden ejecutarse contra servicios en contenedores o entornos similares al de producción. También se emplean en pipelines de branches o pull requests como una barrera de calidad («gate») antes de hacer merge.
  74. 77 77 Test de Componentes (8/8) Los test de componentes

    se integran como un nivel intermedio de validación automatizada: se ejecutan tras la compilación y pruebas unitarias y antes de las pruebas de integración completas o despliegue. De este modo se logra un feedback rápido y concreto por microservicio, agilizando la detección de errores locales y asegurando la estabilidad antes de exponer cambios a todo el sistema. Diferencia fundamental entre pruebas de componentes, contratos e integración: Criterio Pruebas de Componentes Pruebas de Contrato Pruebas de Integración Nivel de aislamiento Alto (microservicio completo en aislamiento) Medio (solo interfaz, sin lógica interna) Bajo (requiere levantar múltiples servicios o recursos) Dependencias externas reales No (usa stubs/fakes) No (usa mocks/stubs para simular proveedor o consumidor) Sí (usa bases reales, colas, servicios en red) Objetivo principal Verificar la lógica del microservicio de forma aislada Verificar que el contrato entre servicios se respeta Validar que varios servicios interactúan correctamente Velocidad de ejecución Alta Muy alta Baja Uso típico en CI/CD Ideal para ejecutarse en cada build Críticas en microservicios. Protegen integraciones Aportan confianza en ambientes reales, pero más lentas Tipo de verificación Caja negra del servicio completo Validación de interfaz esperada entre servicios Prueba de comunicación entre componentes reales Cuándo falla Si el servicio falla internamente o maltrata respuestas de dependencias Si un proveedor rompe la interfaz o falta un campo esperado Si falla una conexión, config o contrato real en ejecución Ejemplo en .NET Test usando WebApplicationFactory + SQLite in-memory + mocks de clientes HTTP PactNet entre servicio consumidor y stub del proveedor Levantar servicio A y B reales con TestServer o Docker Compose
  75. 78 78 Anexo A: Glosario con definiciones técnicas (1/2) •

    Doble de prueba (Test Double): término genérico que abarca cualquier objeto o componente sustituido por uno de prueba en lugar del real. Por ejemplo, se puede usar una clase que implemente una interfaz real, pero con lógica simplificada. Esto incluye varios tipos: dummy (objetos de relleno sin uso funcional), fake (implementación completa simplificada, como una base de datos en memoria), stub y mock. • Stub: doble de prueba que devuelve respuestas preprogramadas (“canned answers”) a las llamadas que reciba durante la prueba. El stub responde solo con lo que se ha codificado para el caso de prueba y no reacciona ante llamadas fuera de ese contexto. Por ejemplo, un stub de repositorio puede devolver siempre el mismo objeto de dominio independientemente de la entrada recibida. • Mock: doble de prueba programado con expectativas sobre las invocaciones que debe recibir. Además de comportarse como un stub, registra llamadas y puede lanzar errores si ocurre una llamada inesperada. Al finalizar la prueba, el mock verifica que se cumplieron todas las expectativas definidas (por ejemplo, que un método fue llamado un número específico de veces). En .NET es común usar bibliotecas como Moq para configurar y verificar estos mocks en pruebas unitarias. • Fixture: conjunto de estado inicial fijo o configuración necesaria para ejecutar pruebas de forma consistente. Un test fixture prepara datos o recursos antes de cada prueba (por ejemplo, poblar una base de datos con registros conocidos o instanciar objetos de prueba con valores determinados) de modo que las pruebas sean repetibles y no dependan del estado dejado por ejecuciones previas. Por ejemplo, en xUnit/.NET se utilizan métodos de configuración ([SetUp], [TestInitialize]) para crear este entorno común.
  76. 79 79 Anexo A: Glosario con definiciones técnicas (2/2) •

    Pruebas End-to-End (E2E): pruebas que simulan flujos de trabajo completos del usuario desde el inicio hasta el fin del sistema. Estas pruebas automáticas ejecutan escenarios reales, transitando por todos los componentes involucrados (por ejemplo, interfaz de usuario → servicios web → base de datos) y validan que el sistema integrado se comporta correctamente. Verifican la interacción entre los distintos módulos y con recursos externos (como bases de datos o hardware) en un entorno cercano al de producción. Su objetivo es asegurar que cada ruta crítica del usuario funcione de forma integrada, aunque suelen ser costosas y lentas de mantener. Nota del autor: La Torre de Testing, una propuesta original Durante el desarrollo de este documento, me propuse ir más allá de las representaciones clásicas del testing como la pirámide, el diamante o el reloj de arena. He realizado una revisión exhaustiva de bibliografía técnica, publicaciones y blogs, y no he encontrado precedentes documentados que utilicen esta metáfora de forma estructurada ni con soporte visual. Por tanto, esta Torre del Testing puede considerarse una propuesta original, desarrollada durante la elaboración de este libro como modelo complementario al pensamiento tradicional. Espero que te ayude a visualizar mejor cómo estructurar, automatizar y decidir tus pruebas no solo por niveles, sino por coste, contexto, frecuencia y propósito.
  77. 80 80 Anexo B: Antipatrones en Estrategias de Testing (1/3)

    • Compartir contratos/modelos entre servicios: El antipatrón “Componentes de Contratos” consiste en crear un paquete (por ejemplo, un NuGet) con las clases de contrato o modelos de datos compartidos para que varios microservicios las consuman. Aunque parece facilitar la consistencia, genera acoplamiento rígido: todos los consumidores dependen de ese paquete y cualquier cambio en el contrato obliga a actualizar todos los servicios relacionados. Además, si el ecosistema es políglota, un paquete .NET no sirve para servicios en otros lenguajes. Para evitarlo, es mejor definir contratos de forma desacoplada (por ejemplo, usando pruebas de contrato), o versionar las APIs de forma independiente en cada servicio para no obligar a sincronizar cambios globales. En mi caso siempre dejo esta opción en un ADR debido a que sale constantemente y algunos clientes prefieren tener este grado de acoplamiento con un NuGet. • Exceso de pruebas E2E: basar la calidad en multitud de pruebas end-to-end puede convertirse en un cuello de botella. Las pruebas E2E suelen ser lentas, frágiles y difíciles de mantener: al recorrer varios sistemas desplegados, cada test tarda más tiempo en ejecutarse y su entorno es complejo de orquestar. En muchos equipos esto conduce a retrasar estas pruebas al final del proceso (p.ej. una etapa de hardening), provocando que fallen casos en producción. Además, los test E2E largos tienden a generar falsos positivos y se marcan como flaky o incluso se deshabilitan, dejando huecos de calidad. Para evitar este antipatrón, se debe priorizar una torre pirámide de pruebas balanceada: usar tests unitarios y de integración/componentes para la mayoría de las verificaciones, reservando pocas pruebas E2E solo para flujos críticos de negocio. De esta forma se obtiene un feedback rápido y estable, reduciendo la dependencia exclusiva de las pruebas de extremo a extremo. José María Flores Zazo
  78. 81 81 Anexo B: Antipatrones en Estrategias de Testing (2/3)

    • Pruebas acopladas al entorno (Local Hero): ocurre cuando una prueba depende de configuraciones locales o recursos instalados en la máquina del desarrollador, pero falla en otros entornos. Por ejemplo, un test que asume que RabbitMQ o Redis están disponibles, o que usa puertos fijos distintos según la máquina de cada dev. Este environment-dependent test suele pasar localmente, pero fallar en CI/otros equipos. La solución es aislar el entorno de prueba usando contenedores o entornos virtualizados (por ejemplo, Docker) que repliquen fielmente el entorno de producción, de modo que cada desarrollador y la CI ejecuten las pruebas bajo las mismas condiciones. Así se elimina la dependencia implícita del entorno del creador del test. • Falta de validación continua (sólo probar en producción): es un antipatrón omitir pruebas en la fase de integración continua y esperar a descubrir fallos hasta el despliegue. Por ejemplo, confiar en que los errores se detectarán en producción equivale a probar sólo en producción, lo que genera largos ciclos de retroalimentación y riesgos altos: los defectos pueden pasar inadvertidos hasta que afectan a usuarios reales. Para evitarlo, las pruebas deben automatizarse e incluirse en cada compilación/commit. Esto implica que una validación mínima (tests unitarios, estáticos y contractuales) se ejecute en CI por cada cambio. Cualquier error debe frenarse antes del merge, manteniendo la integridad del código y evitando que problemas lleguen tarde al usuario final. José María Flores Zazo
  79. 82 82 Anexo B: Antipatrones en Estrategias de Testing (3/3)

    • Dependencias ocultas entre pruebas (Generous Leftovers): sucede cuando una prueba reutiliza datos o estado creado por otra, creando dependencias implícitas en el orden de ejecución. Por ejemplo, un test que inserta datos en la base de datos y un test posterior que asume esos datos existentes. Esto rompe el aislamiento de pruebas y hace que la suite sea frágil: si cambias el orden o se ejecuta aislada alguna prueba, fallará inesperadamente. Para evitarlo, cada prueba debe ser independiente: utilizar fixtures bien definidos o métodos de setup que siempre creen el estado inicial esperado, y limpiar cualquier dato residual. Ejecutar los tests en orden aleatorio (por ejemplo, con executionOrder="random") ayuda a detectar estas dependencias ocultas. El objetivo es lograr que cualquier test pueda ejecutarse solo y repetirse con éxito en cualquier momento.
  80. 83 83 Anexo C: Caja Negra vs Caja Blanca (1/2)

    Las definiciones, deberían estar en el Anexo A, pero es preferible ponerlo todo en esta sección: • Pruebas de Caja Negra: Se centran en validar el comportamiento externo de la aplicación sin conocer su lógica interna. El tester trata al sistema como una «caja negra»: envía entradas (peticiones, acciones de usuario, datos de prueba) y verifica las salidas o efectos observables. Este enfoque es típico de pruebas funcionales, de aceptación o E2E. Es útil especialmente en fases iniciales de desarrollo o pruebas de aceptación, donde se comprueba que se cumplen los requisitos y la funcionalidad desde la perspectiva del usuario final. Por ejemplo, en .NET se podría probar un API REST mediante peticiones HTTP y validar las respuestas JSON sin mirar el código del controlador. La ventaja es que simula el uso real y detecta errores de integración o de interfaz; la desventaja es que partes internas del código podrían quedar sin cubrir si no existen pruebas dedicadas a ellas. • Pruebas de Caja Blanca: Se basan en el conocimiento de la implementación interna del software. El tester diseña casos que ejercitan ramas lógicas, bucles y condiciones del código, buscando cubrir todas las rutas posibles. Es el enfoque típico de pruebas unitarias en .NET, donde los desarrolladores ejecutan métodos directamente y comprueban resultados según la lógica interna. Las pruebas de caja blanca se utilizan, por ejemplo, para verificar la seguridad, el rendimiento o la cobertura de código tras cambios en etapas avanzadas del desarrollo. Al conocer la estructura del código, el tester puede diseñar escenarios que exploran casos límite o sentencias no cubiertas. Requiere mayor conocimiento técnico y tiempo para crear casos exhaustivos, pero permite detectar errores ocultos en la lógica y garantizar la calidad interna del software.
  81. 84 84 Anexo C: Caja Negra vs Caja Blanca (2/2)

    Combinación efectiva En la práctica, los enfoques de caja negra y blanca son complementarios. Una estrategia de pruebas robusta combina ambos niveles: usar pruebas blancas (unitarias e internas) para asegurar la corrección de la implementación y alcanzar cobertura de código, y usar pruebas negras (funcionales, de contrato, E2E) para validar la experiencia del usuario y la integración entre componentes. Por ejemplo, en un microservicio .NET puede haber numerosos tests unitarios (caja blanca) para su lógica de negocio, tests de contrato que verifican la API expuesta (caja negra desde la perspectiva del consumidor) y algunos tests end-to-end limitados que ejercitan flujos completos del sistema. La documentación recomienda aplicar caja negra en las primeras fases o pruebas de aceptación y reserva caja blanca para validaciones más profundas del código en desarrollo posterior. De esta forma se obtiene un “ciclo de prueba” completo: se detectan rápido los fallos lógicos con pruebas unitarias (caja blanca) y se corrobora que las funcionalidades funcionan correctamente desde fuera con pruebas de caja negra, logrando una cobertura integral de calidad.
  82. 85 85 Epílogo Este libro nace de la práctica, la

    observación constante y el compromiso con la calidad en el desarrollo de software. Su propósito ha sido aportar claridad y profundidad a dos disciplinas frecuentemente subestimadas, pero absolutamente esenciales en arquitecturas modernas: las pruebas de contrato y las pruebas de componentes. Lejos de ser un compendio teórico, el contenido ha sido construido desde la experiencia directa, con el objetivo de que cada persona, independientemente de su nivel previo, pueda incorporar estas prácticas de forma progresiva y efectiva en su día a día profesional. En un contexto donde los microservicios, la automatización y la entrega continua han dejado de ser aspiracionales para convertirse en expectativas mínimas, las pruebas ya no son una opción secundaria. Son una parte integral del diseño, del flujo de trabajo y de la estrategia de resiliencia de cualquier sistema distribuido. Este libro ha querido reflejar esa realidad y ofrecer herramientas concretas para afrontarla con solidez técnica y criterio arquitectónico. El concepto de la Torre del Testing no pretende sustituir a otros modelos, sino ofrecer una representación alternativa que permita razonar mejor sobre coste, frecuencia, impacto y propósito de cada tipo de prueba. En vez de limitarse a una forma clásica como la pirámide, esta torre busca reflejar las verdaderas decisiones que los equipos deben tomar: qué pruebas deben ser constantes, cuáles condicionales, y cuáles reservadas para contextos especiales o críticos. Quiero agradecer al lector/a su tiempo y atención. Concluir un libro técnico no siempre es fácil: requiere voluntad de comprender y espíritu crítico. Si este contenido ha sido útil para tomar mejores decisiones, para desafiar algunos hábitos, o simplemente para tener más claridad en proyectos reales, entonces este esfuerzo ha cumplido su cometido. Como toda disciplina en evolución, el testing también cambia. Mi invitación final es a seguir aprendiendo, a experimentar con sentido, y a mantener la calidad como un compromiso técnico, ético y profesional. Nos seguiremos encontrando en el código, en la comunidad y en futuros libros. José María Flores Zazo