Cobertura alta, defectos en producción: qué probar en un servicio de pagos
Un servicio de pagos tiene 87% de cobertura de líneas y 0 pruebas de integración. En el último trimestre, 6 de 9 incidentes en producción provinieron de interacciones entre el servicio y la pasarela externa: respuestas 200 con cuerpo de error, timeouts de 30 s tratados como éxito y webhooks duplicados que generaron doble cargo. Las pruebas unitarias mockean la pasarela con respuestas ideales. El equipo propone 'subir la cobertura al 95%' como remedio. El líder técnico pide un plan alternativo antes de la próxima release, con presupuesto de 2 sprints y sin acceso a un entorno real de la pasarela (solo un sandbox que a veces devuelve 500).
1. Evalúa la propuesta de subir la cobertura al 95%. ¿Por qué no ataca los incidentes observados?
Read the worked answer
La cobertura mide ejecución, no verificación de comportamiento en fronteras. Con 87% ya se ejecutan casi todas las líneas; los fallos viven en contratos que los mocks simplifican:
- El mock siempre devuelve 200 con cuerpo válido → nunca se prueba '200 con cuerpo de error', que es un caso real del proveedor.
- El mock responde al instante → el timeout de 30 s y su tratamiento como éxito no se ejercitan.
- El mock no reenvía webhooks → la idempotencia no se prueba.
Subir al 95% añadiría líneas triviales (getters, ramas de logging) sin tocar estas fronteras. Es una métrica de vanidad en este contexto.
Reencuadre: el objetivo no es cobertura, es riesgo cubierto. Los 6 incidentes definen exactamente qué probar: contrato de respuesta, semántica de timeout, idempotencia de webhooks.
2. Diseña el conjunto de pruebas que habría detectado los 6 incidentes, dado que solo hay sandbox inestable.
Read the worked answer
1. Pruebas de contrato (consumer-driven):
- Grabar respuestas reales del sandbox (incluidas 200 con
status:error, cuerpos truncados,null) y reproducirlas con un stub local determinista. - Verificar que el servicio solo trata como éxito el código de negocio esperado, no el HTTP 200.
2. Pruebas de timeout y resiliencia:
- Stub que retrasa 31 s → aserción: se aborta, se marca
unknowny no se confirma el cargo. - Stub que devuelve 500 intermitente → verificar reintento con backoff y tope.
3. Idempotencia de webhooks:
- Enviar el mismo evento dos veces con el mismo
event_id→ aserción: un solo cargo, segunda respuesta 200 idempotente. - Enviar eventos fuera de orden → estado consistente.
4. Pruebas de integración contra sandbox en pipeline nocturno, tolerantes a 500 (marcar skip y alertar, no fallar el build), para detectar deriva del proveedor.
Criterio de éxito: cada uno de los 6 incidentes se reproduce como prueba que falla antes del fix y pasa después (prueba de regresión).
3. Con 2 sprints y sandbox inestable, prioriza. ¿Qué dejas fuera y cómo justificas el riesgo residual?
Read the worked answer
Sprint 1 (mayor impacto):
- Contrato de respuesta (cubre '200 con error').
- Timeout → estado
unknowncon reconciliación posterior (cubre el doble cargo por timeout tratado como éxito). - Idempotencia por
event_iden webhooks (cubre el doble cargo por duplicados).
Estos tres cubren 6/6 incidentes conocidos.
Sprint 2:
- Suite de integración nocturna contra sandbox con tolerancia a fallos.
- Reconciliación automática de pagos en estado
unknown.
Fuera de alcance: subir cobertura al 95%, fuzzing exhaustivo, pruebas de carga de la pasarela. No reducen el riesgo observado en el plazo.
Riesgo residual declarado: el sandbox no reproduce todos los comportamientos del proveedor; podría aparecer un modo de fallo nuevo. Mitigación: telemetría de respuestas no reconocidas en producción y alerting cuando aparezca un código de negocio no mapeado. Se acepta el riesgo con vigilancia, no en silencio.