El Desafío del Testing de Microservicios

Las arquitecturas de microservicios dividen una aplicación monolítica en docenas o cientos de servicios desplegables independientemente. Cada servicio maneja sus propios datos, se comunica a través de la red y puede estar escrito en un lenguaje diferente. Esto trae flexibilidad de deployment pero aumenta dramáticamente la complejidad del testing.

En un monolito, pruebas una aplicación. En microservicios, pruebas muchos servicios y las interacciones entre ellos. Un bug puede no existir en ningún servicio individual — puede aparecer solo cuando el Servicio A envía un mensaje específico al Servicio B, que dispara el Servicio C.

La Pirámide de Testing para Microservicios

La pirámide de testing tradicional (unit > integration > E2E) sigue aplicando, pero necesita adaptación para sistemas distribuidos.

Capa 1: Unit Tests (Base)

Prueban funciones y clases individuales dentro de un solo servicio. Son rápidos, numerosos y se ejecutan sin dependencias externas.

Servicio A
├── Unit tests para lógica de negocio
├── Unit tests para transformación de datos
└── Unit tests para reglas de validación

Objetivo: Verificar que la lógica interna de cada servicio es correcta.

Capa 2: Component Tests

Esta capa es única de microservicios. Un component test verifica un solo servicio como un todo, con sus dependencias externas (bases de datos, otros servicios) reemplazadas por test doubles.

┌─────────────────────────┐
│     Component Test       │
│  ┌───────────────────┐  │
│  │   Servicio A       │  │
│  │   (código real)    │  │
│  └───────────────────┘  │
│  ┌─────────┐ ┌────────┐ │
│  │ Mock DB │ │Mock Svc│ │
│  │         │ │   B    │ │
│  └─────────┘ └────────┘ │
└─────────────────────────┘

Los component tests inician el servicio, envían solicitudes HTTP a sus endpoints reales y verifican las respuestas. La diferencia con los integration tests es que todas las llamadas externas están mockeadas.

Ejemplo con base de datos en memoria:

describe('User Service Component', () => {
  let app;

  beforeAll(async () => {
    app = await startService({
      database: 'sqlite::memory:',
      paymentService: mockPaymentService,
      emailService: mockEmailService,
    });
  });

  test('POST /users crea un usuario y retorna 201', async () => {
    const response = await request(app)
      .post('/users')
      .send({ name: 'Alice', email: 'alice@example.com' });

    expect(response.status).toBe(201);
    expect(response.body.name).toBe('Alice');
  });

  test('POST /users con email duplicado retorna 409', async () => {
    await request(app)
      .post('/users')
      .send({ name: 'Alice', email: 'alice@example.com' });

    const response = await request(app)
      .post('/users')
      .send({ name: 'Bob', email: 'alice@example.com' });

    expect(response.status).toBe(409);
  });
});

Capa 3: Integration Tests

Verifican que dos o más servicios se comunican correctamente. Estos tests usan dependencias reales (o containerizadas).

Integración servicio-a-servicio:

  • Servicio A llama a la API del Servicio B — ¿el formato de la solicitud coincide?
  • Servicio A publica un evento — ¿el Servicio B lo consume correctamente?

Integración servicio-a-infraestructura:

  • Servicio A se conecta a PostgreSQL — ¿funcionan las queries?
  • Servicio A publica en Kafka — ¿se entregan los mensajes?
  • Servicio A lee del cache Redis — ¿se respetan los TTLs?

Capa 4: Contract Tests

Los contract tests verifican que la interfaz entre dos servicios permanezca compatible. Un consumidor define qué espera; el proveedor verifica que puede cumplir esas expectativas.

Esta capa es crítica en microservicios porque los servicios se despliegan independientemente. Sin contract tests, el Servicio Proveedor podría desplegar un cambio que rompa al Servicio Consumidor sin descubrirlo hasta producción.

Capa 5: End-to-End Tests (Tope)

Los tests E2E verifican flujos de negocio completos a través de múltiples servicios. Son los más realistas pero también los más frágiles, lentos y costosos.

Minimiza los tests E2E. Solo cubre rutas de negocio críticas: registro de usuario, procesamiento de pagos, flujos core.

El Testing Honeycomb

Algunos equipos prefieren el testing honeycomb sobre la pirámide para microservicios. En este modelo, los integration y component tests son la capa más ancha — no los unit tests. La razón: en microservicios, la mayoría de los bugs ocurren en las fronteras de los servicios, no dentro de la lógica de negocio.

Estrategias de Testing por Patrón de Comunicación

Sincrónico (REST/gRPC)

Cuando el Servicio A hace una llamada HTTP o gRPC al Servicio B:

  1. Component test: Mockea la respuesta del Servicio B en los tests del Servicio A.
  2. Contract test: Verifica que los schemas request/response coincidan.
  3. Integration test: Levanta ambos servicios (Docker Compose) y verifica la llamada real.

Asincrónico (Eventos/Mensajes)

Cuando el Servicio A publica un evento que consume el Servicio B:

  1. Component test: Verifica que el Servicio A publica la forma correcta del evento. Verifica que el Servicio B procesa un evento dado correctamente.
  2. Contract test: Define el schema del evento como un contrato.
  3. Integration test: Publica un evento real y verifica que el Servicio B lo procesa.

Coreografía vs Orquestación

En coreografía, los servicios reaccionan a eventos independientemente — el testing requiere verificar la cadena de eventos. En orquestación, un coordinador central (patrón Saga) maneja el flujo — probar el orquestador se convierte en prioridad.

Estrategia de Ambientes

Desarrollo Local

Usa Docker Compose para ejecutar servicios dependientes localmente:

services:
  user-service:
    build: ./user-service
    ports: ["3001:3001"]
    depends_on: [postgres, redis]
  order-service:
    build: ./order-service
    ports: ["3002:3002"]
    depends_on: [postgres, kafka]
  postgres:
    image: postgres:16
  redis:
    image: redis:7
  kafka:
    image: confluentinc/cp-kafka:7.5.0

Pipeline de CI

Ejecuta component e integration tests en CI usando contenedores Docker. Mantén los tests E2E en un stage separado que se ejecute con menor frecuencia (nightly o en ramas de release).

Ejercicio: Diseña una Estrategia de Testing de Microservicios

Eres el QA Lead de una plataforma de e-commerce con los siguientes microservicios:

Arquitectura del Sistema

┌──────────┐     ┌──────────┐     ┌──────────┐
│  API     │────▶│  User    │────▶│  Auth    │
│  Gateway │     │  Service │     │  Service │
└──────────┘     └──────────┘     └──────────┘
      │
      ▼
┌──────────┐     ┌──────────┐     ┌───────────────┐
│  Order   │────▶│ Payment  │────▶│ Notification  │
│  Service │     │  Service │     │  Service      │
└──────────┘     └──────────┘     └───────────────┘
      │
      ▼
┌──────────┐     ┌──────────┐
│ Inventory│────▶│ Shipping │
│  Service │     │  Service │
└──────────┘     └──────────┘

Patrones de comunicación:

  • API Gateway → Servicios: REST (sincrónico)
  • Order Service → Payment Service: REST (sincrónico)
  • Order Service → Inventory Service: eventos Kafka (asincrónico)
  • Payment Service → Notification Service: eventos Kafka (asincrónico)

Tarea 1: Distribución de Capas de Tests

Para cada capa de testing, lista los tests específicos que escribirías:

Unit tests (por servicio):

ServicioQué Probar con Unit Tests
Order ServiceCálculo de total, lógica de descuentos, máquina de estados del pedido
Payment ServiceValidación de pago, cálculo de reembolso, conversión de moneda
Inventory ServiceLógica de verificación de stock, expiración de reservas
Notification ServiceRenderizado de templates, selección de canal (email/SMS/push)

Component tests:

Para cada servicio, define 3-5 component tests.

Contract tests:

Define pares consumidor-proveedor:

ConsumidorProveedorContrato
Order ServicePayment ServicePOST /payments: schema request/response
Order ServiceInventory ServiceSchema evento ReserveStock
Payment ServiceNotification ServiceSchema evento PaymentConfirmed

E2E tests (solo rutas críticas):

  1. Usuario se registra → inicia sesión → crea pedido → pago exitoso → inventario actualizado → envío iniciado → notificación enviada.
  2. Usuario crea pedido → pago falla → pedido cancelado → inventario liberado.
  3. Usuario crea pedido → solicita reembolso → reembolso procesado → notificación enviada.

Tarea 2: Escenarios de Falla

Para cada servicio, define qué sucede cuando falla y cómo lo probarías:

Servicio CaídoImpactoEstrategia de Test
Payment ServicePedidos no pueden completarseVerificar que Order Service encola reintentos
Inventory ServiceVerificación de stock fallaVerificar que Order Service rechaza o encola pedidos
Notification ServiceUsuarios no notificadosVerificar que pedidos completan (notificación no es crítica)

Tarea 3: Diseño del Pipeline de CI

Diseña un pipeline de CI con estos stages:

Stage 1: Build & Unit Tests (por servicio, paralelo) — 2 min
Stage 2: Component Tests (por servicio, paralelo) — 5 min
Stage 3: Contract Tests (consumidor + proveedor, paralelo) — 3 min
Stage 4: Integration Tests (Docker Compose) — 10 min
Stage 5: E2E Smoke Tests (staging) — 15 min (solo nightly)

Entregables

  1. Un documento de estrategia de testing listando conteo de tests por capa por servicio.
  2. Un diagrama del pipeline de CI mostrando stages de tests y gates.
  3. Una matriz de riesgos identificando qué fallas de servicio son mayor prioridad para probar.