Módulo 3: Integration Testing & Estrategias No-Determinísticas
1. Introducción: Integration Testing para AI
Descripción
Los módulos 1-2 resolvieron testing de forma determinística con mocks. Pero hay escenarios donde necesitas el LLM real: validar que el modelo produce outputs útiles, detectar regresiones al cambiar de modelo, verificar el flujo end-to-end. El problema: con LLM real, el mismo input puede producir outputs diferentes. Este módulo enseña las estrategias probadas para manejar eso — sin perder la confianza en tu test suite.
El límite de los mocks
En el Módulo 2 aprendiste que los mocks son poderosos: rápidos, gratuitos, determinísticos. Pero tienen un límite fundamental:
# Con mock, sabes que:
# ✅ Tu parser maneja bien el JSON
# ✅ Tu processor normaliza el score correctamente
# ✅ La estructura del output cumple el contrato
# ✅ Los errores de API se manejan gracefully
# Con mock, NO sabes:
# ❌ ¿El LLM realmente entiende el prompt?
# ❌ ¿El resumen es coherente con el texto original?
# ❌ ¿El modelo sigue produciendo la misma calidad después de una actualización?
# ❌ ¿El prompt funciona bien para todos los idiomas e inputs edge?
Los integration tests con LLM real llenan este gap. No reemplazan los unit tests — los complementan.
El desafío central: non-determinism
El problema fundamental de testear con LLM real:
# Mismo input, dos ejecuciones distintas:
text = "Python es un lenguaje de programación interpretado y de alto nivel."
result_1 = summarize(text)
# → {"summary": "Python es un lenguaje de alto nivel interpretado.", "score": 0.9}
result_2 = summarize(text)
# → {"summary": "Python: lenguaje interpretado de alto nivel.", "score": 0.92}
# Ambos son CORRECTOS. Pero son DIFERENTES.
# assert result_1 == result_2 → FALLA
# assert result_1["summary"] == "Python es un lenguaje interpretado." → FALLA también
Las estrategias de este módulo resuelven exactamente este problema: cómo hacer assertions que sean significativas cuando el output varía.
El espectro de assertions
Hay un continuo de estrategias, desde la más estricta a la más flexible:
Más estricto Más flexible
─────────────────────────────────────────────────────────────────►
[Igualdad exacta] [Contiene] [Regex] [Propiedades] [Semántica]
result == kw in re. len in range similarity
"exact text" result match() 0.85-0.95
La regla: Usa la assertion más estricta que sea estable. Si el output exacto puede variar, sube un nivel en el espectro hasta que la assertion sea robusta sin perder valor de detección.
| Estrategia | Cuándo usarla |
|---|---|
| Igualdad exacta | Con mocks (determinístico) |
| Contiene keyword | Cuando el output DEBE mencionar algo específico |
| Regex | Formato o estructura parcial |
| Propiedades (rango, tipo) | Invariantes que siempre se cumplen |
| Similitud semántica | Cuando el significado importa, no las palabras exactas |
Cuándo usar integration tests
| Escenario | Usa Mock | Usa LLM Real |
|---|---|---|
| Desarrollo diario | ✅ | ❌ (caro, lento) |
| Validar estructura del output | ✅ | Opcional |
| Validar calidad semántica | ❌ | ✅ |
| Detectar model drift | ❌ | ✅ |
| CI en cada commit | ✅ | ❌ |
| CI en main/pre-release | ✅ | ✅ (con budget cap) |
| Debugging de output incorrecto | Opcional | ✅ |
Regla de proporciones: 80% unit tests (mock), 20% integration tests (LLM real). Los integration tests son el complemento estratégico, no la base.
El costo como constraint real
Los integration tests cuestan dinero. Este es un constraint de diseño, no un detalle:
Ejemplo real:
- 100 integration tests
- Cada test: 500 tokens input + 200 tokens output
- Modelo: gpt-4o-mini ($0.15/1M input, $0.60/1M output)
Costo por run:
Input: 100 × 500 × $0.15/1M = $0.0075
Output: 100 × 200 × $0.60/1M = $0.0120
Total: ~$0.02 por run
Con 50 commits/día: $1/día = $30/mes SOLO en tests
Con gpt-4o ($2.50/1M input, $10/1M output):
Mismo setup: ~$0.25 por run × 50 = $12.50/día = $375/mes
Por esto los budget controls no son opcionales — son parte del diseño de la test suite.
Las cinco estrategias del módulo
Este módulo enseña cinco estrategias concretas:
Estrategia 1: Tests End-to-End (Cápsula 2)
Ejecutar el flujo completo con LLM real. Assertions flexibles sobre el output. Budget controls obligatorios.
Estrategia 2: Decision Framework Mock vs Real (Cápsula 3)
Criterios claros para decidir cuándo cada tipo de test aporta más valor. Configuración por entorno.
Estrategia 3: Semantic Similarity Assertions (Cápsula 4)
Comparar el significado del output con embeddings. Elimina la frágil igualdad de strings manteniendo assertions significativas.
Estrategia 4: Property-Based Testing (Cápsula 5)
Definir invariantes que siempre deben cumplirse. Hypothesis genera inputs automáticamente. Más eficaz que casos de test manuales.
Estrategia 5: Flaky Test Management (Cápsula 6)
Retry logic, tolerance thresholds, quarantine. Para cuando la varianza del LLM hace que los tests fallen ocasionalmente.
La transformación mental del módulo
Antes de este módulo:
→ "No puedo testear con LLM real — el output siempre es diferente"
→ "Los integration tests son demasiado caros"
→ "Mis tests de AI son flaky, es normal"
Después de este módulo:
→ "Non-determinism es manejable con las estrategias correctas"
→ "Los integration tests son estratégicos — pocos, bien elegidos"
→ "Flakiness es una señal que requiere atención, no algo que se ignora"
Prerequisitos del módulo
Antes de comenzar, asegúrate de tener del Módulo 2:
# 1. Suite de unit tests funcionando
pytest -m unit -v # Debe pasar 100%
# 2. La app de referencia funcionando
python -c "from app.sentiment import analyze_sentiment; print('OK')"
# 3. API key configurada (para integration tests)
echo $OPENAI_API_KEY # Debe mostrar la key
# 4. Dependencias adicionales para este módulo
pip install pytest-rerunfailures hypothesis sentence-transformers numpy
Roadmap del módulo
| # | Cápsula | Tipo | Dependency |
|---|---|---|---|
| 01 | Introducción | Conceptual | M2 completo |
| 02 | Tests end-to-end | Técnico | OpenAI API key |
| 03 | LLM real vs mocks | Estrategia | — |
| 04 | Semantic similarity assertions | Técnico | OpenAI embeddings o sentence-transformers |
| 05 | Property-based testing | Técnico | Hypothesis |
| 06 | Flaky test management | Técnico | pytest-rerunfailures |
| 07 | Proyecto Integration Test Suite | Proyecto | Todo lo anterior |
| 08 | Resumen y troubleshooting | Cierre | — |
Conexión con los módulos anteriores
Módulo 1: Configuró pytest, definió markers, smoke tests
↓
Módulo 2: Unit tests con mocks, contract tests, parsers
↓
Módulo 3: Integration tests, LLM real, non-determinism strategies
↓
Módulo 4: Guardrails implementados y testeados con M3 strategies
El Módulo 3 no solo añade integration tests — también sirve como base para los módulos siguientes donde testearás guardrails, validaciones de input/output, y comportamientos de seguridad que requieren el LLM real para ser validados correctamente.
Ejercicios
Ejercicio 1: Identificar el gap de los mocks
Para tu app actual, identifica 3 cosas que el mock NO puede validar pero que son importantes para producción:
Ver guía
Ejemplos comunes:
- Calidad semántica: "¿El resumen captura los puntos clave del texto original?" — El mock solo valida estructura.
- Prompt robustez: "¿El prompt funciona igual de bien para textos en inglés, español, y portugués?" — El mock retorna lo que tú le programas, no lo que el LLM real produciría.
- Model drift: "Después de actualizar de gpt-4o a gpt-4o-mini, ¿el output sigue siendo suficientemente bueno?" — Necesitas comparar con el LLM real.
- Edge cases reales: "Para textos con emojis, lenguaje informal o jerga, ¿el parser sigue funcionando?" — El LLM real produce variaciones que no anticipas en mocks.
Ejercicio 2: Calcular el costo de tu test suite
Estima el costo mensual de correr integration tests para tu app:
- 20 integration tests
- Cada test: 600 tokens input + 150 tokens output
- Modelo: gpt-4o-mini
- Frecuencia: 2 veces al día (CI en main)
Ver cálculo
Por run:
Input: 20 × 600 × $0.15/1M = $0.0018
Output: 20 × 150 × $0.60/1M = $0.0018
Total: ~$0.0036 por run
Por mes (30 días × 2 runs/día):
$0.0036 × 60 = ~$0.22/mes
Con gpt-4o (17x más caro):
$0.22 × 17 ≈ $3.74/mes
Conclusión: Con gpt-4o-mini es muy accesible.
Con gpt-4o, considera ejecutar menos frecuentemente.
Ejercicio 3: Diseñar el espectro de assertions
Para un test E2E de resumen de texto, diseña una assertion en cada nivel del espectro:
Ver solución
# Nivel 1: Igualdad exacta (frágil con LLM real)
assert result["summary"] == "Python es un lenguaje interpretado y de alto nivel."
# Nivel 2: Contiene keyword (más estable)
assert "python" in result["summary"].lower()
# Nivel 3: Regex (formato específico)
assert re.search(r'\w+ es \w+', result["summary"]) # Tiene forma "X es Y"
# Nivel 4: Propiedades (invariantes)
assert 10 <= len(result["summary"].split()) <= 100 # Entre 10 y 100 palabras
assert 0 <= result["confidence"] <= 1
# Nivel 5: Similitud semántica
assert_semantically_similar(
result["summary"],
"Python es un lenguaje de programación de alto nivel",
threshold=0.8
)
Recomendación: Para integration tests con LLM real, usa niveles 2-5. El nivel 1 solo con mocks.
Ejercicio 4: Non-determinism en tu proyecto
¿Cuál es el test más "frágil" que tienes actualmente? ¿Qué estrategia del módulo lo haría más robusto?
Ver guía
Evaluación:
- Si el test hace
assert result == "texto exacto"→ Cambiar a semantic similarity o property-based - Si el test falla frecuentemente por timeout → Añadir retry logic y skip condicional
- Si el test llama al LLM real en cada commit → Convertir a mock (o limitarlo con skip por env var)
- Si el test verifica que "el resumen es bueno" → Definir la propiedad específica: longitud, keywords, similitud
Ejercicio 5: Planificar tu integration test suite
Antes de escribir el código, planifica:
- ¿Cuántos integration tests tendrá tu suite?
- ¿Cuándo se ejecutarán (cada commit / en main / nightly)?
- ¿Cuál es el budget máximo por run?
- ¿Qué estrategia usarás para cada test?
Ver plantilla de planificación
Integration Test Suite Plan:
Tests: 10-15 (regla: pocos y bien elegidos)
Ejecución: En merge a main (no en cada commit)
Budget: $0.10 máximo por run
Test 1: E2E flujo principal (resumen)
→ Estrategia: propiedades (longitud, estructura)
→ Modelo: gpt-4o-mini
→ Tokens estimados: 800
Test 2: Calidad semántica del resumen
→ Estrategia: semantic similarity threshold=0.8
→ Modelo: gpt-4o-mini
→ Tokens estimados: 1200 (incluye embeddings)
Test 3: Flujo con input edge (texto muy corto)
→ Estrategia: propiedades + manejo de error
→ Modelo: gpt-4o-mini
→ Tokens estimados: 300
...
Resumen
- Integration tests complementan (no reemplazan) los unit tests con mock
- Non-determinism es manejable con las estrategias correctas: semantic similarity, property-based, flaky management
- El espectro de assertions: de igualdad exacta a similitud semántica — usa la más estricta que sea estable
- Costo es un constraint real — budget controls desde el diseño, no como afterthought
- Proporción: 80% unit tests, 20% integration tests estratégicos
Recursos adicionales
- Testing ML Systems — Google — Prácticas de equipos grandes
- OpenAI Pricing — Precios actuales para calcular budget
- The Test Pyramid — Martin Fowler — Proporciones de tipos de tests
- Non-determinism in Tests — Martin Fowler — Estrategias generales
- Módulo 2: Unit Testing LLM Applications — Prerequisito
- Hypothesis Documentation — Para property-based testing (Cápsula 5)
- sentence-transformers — Embeddings locales para semantic assertions