Diseño y estrategia de pruebas en System Design | Nicolás Garzón
Una estrategia de pruebas no comienza escogiendo frameworks. Comienza identificando qué afirmaciones del diseño deben demostrarse, qué riesgo cubre cada prueba y qué evidencia permitirá decidir.
Diseñar para probar significa hacer observables y controlables las reglas, estados, contratos y fallos importantes. La estrategia conecta necesidades con evidencia sin depender de una pirámide aplicada mecánicamente.
Texto
Copiar objetivo o riesgo
→ requisito verificable
→ escenario
→ oráculo esperado
→ nivel de prueba
→ ambiente y datos
→ evidenciaUna prueba sin criterio puede ejecutar mucho código y demostrar muy poco.
¿Qué comportamientos no pueden fallar?
¿Qué reglas necesitan pruebas rápidas y exhaustivas?
¿Qué colaboraciones reales deben integrarse?
¿Qué contratos pueden romper consumidores?
¿Qué recorridos justifican una prueba completa?
¿Qué atributos de calidad requieren medición?
¿Qué datos y ambientes hacen reproducible la evidencia?
¿Qué riesgos no quedan cubiertos y por qué?
Texto
Copiar RNF-04
El 95 % de las consultas de catálogo debe responder en menos de 300 ms
con 150 solicitudes por segundo y catálogo de 100 000 productos.
Evidencia
→ prueba de carga con distribución realista
→ p50, p95, p99 y errores
→ uso de CPU, memoria, conexiones y base de datos
→ resultado comparado con el umbral“Probar que sea rápido” no define carga, percentil, ambiente ni criterio.
Comprueban reglas, invariantes, transformaciones y decisiones de forma aislada y rápida.
Cálculo de total.
Transiciones permitidas.
Políticas de descuento.
Validación de cantidades.
Construcción de value objects.
No requieren que cada clase tenga una prueba propia. La unidad es el comportamiento pequeño que aporta confianza.
Comprueban colaboración con infraestructura real o representativa:
Persistencia y restricciones.
Transacciones.
Serialización.
Cache.
Broker.
Servicio externo mediante sandbox o stub contractual.
Una prueba con repositorio totalmente simulado no demuestra que claves, transacciones o consultas funcionen.
Protegen compatibilidad entre consumidor y proveedor.
Esquema de request y response.
Campos requeridos y opcionales.
Errores estables.
Versiones de eventos.
Compatibilidad hacia atrás.
Firmas y headers de webhooks.
No sustituyen una prueba de integración: un contrato válido puede estar implementado de forma incorrecta.
Demuestran recorridos críticos entre componentes desplegados. Son valiosas para unas pocas capacidades de alto valor, pero suelen ser más lentas, frágiles y difíciles de diagnosticar.
Registrar una venta y descontar inventario.
Crear, preparar y entregar pedido.
Cancelar y confirmar compensaciones.
Permiten descubrir problemas que no estaban formalizados y validar con negocio u operación que el resultado es útil.
Incluyen rendimiento, seguridad, accesibilidad, usabilidad, resiliencia, recuperación, compatibilidad y migración.
Toda prueba necesita decidir qué resultado es correcto.
Texto
Copiar entrada
+ estado inicial
+ evento
→ salida
+ estado final
+ efectos permitidos
+ efectos prohibidos
Pedido termina en estado válido.
Stock vuelve una sola vez.
Reembolso no se duplica.
Evento conserva correlación.
Una repetición devuelve el mismo resultado lógico.
Comprobar solo el status HTTP no demuestra las invariantes.
Tiempo, aleatoriedad, IDs y servicios externos deben poder controlarse sin alterar las reglas.
Expón eventos, logs, métricas o consultas de diagnóstico suficientes. No conviertas tests en consultas directas a detalles internos innecesarios.
Contratos pequeños y responsabilidades cohesionadas facilitan aislar fallos.
Fixtures, factories o builders deben expresar intención y evitar objetos gigantes irrelevantes.
Permiten repetir pruebas y recuperarse de incertidumbre.
Cada prueba debe conocer qué datos crea, reutiliza y elimina. Paralelismo no debe introducir interferencia accidental.
No todo merece la misma inversión.
Texto
Copiar riesgo alto + detección difícil
→ pruebas profundas, múltiples niveles y monitoreo
riesgo bajo + cambio frecuente
→ pruebas rápidas enfocadas en comportamientoPara cada riesgo registra:
Fallo temido.
Probabilidad e impacto.
Prevención en diseño.
Prueba antes de producción.
Detección en producción.
Recuperación.
Dos solicitudes leen la misma disponibilidad y reservan las últimas unidades.
Prueba unitaria de política de disponibilidad.
Prueba de integración con dos transacciones concurrentes.
Verificación de constraint, lock o control optimista.
Prueba de retry ante conflicto.
Métrica de rechazos y stock negativo en operación.
Un test secuencial no reproduce la carrera.
Datos sintéticos para reglas y límites.
Datos anonimizados para distribuciones realistas.
Datos generados para volumen.
Casos extremos diseñados deliberadamente.
Valores mínimos y máximos.
Nulos y formatos inválidos.
Unicode y zonas horarias.
Duplicados.
Estados históricos.
Relaciones rotas cuando se prueba recuperación.
No copies producción sin controles de privacidad.
Un ambiente útil debe declarar diferencias respecto a producción.
Texto
Copiar igual o equivalente
→ versión de runtime
→ esquema
→ configuración crítica
→ topología relevante
posible diferencia
→ escala
→ proveedor sandbox
→ datos sintéticosUna prueba aprobada en un entorno sin latencia externa no demuestra el comportamiento distribuido real.
Fake: implementación simplificada y controlable.
Stub: respuestas predefinidas.
Mock: verifica interacciones esperadas.
Sandbox: proveedor real en entorno de prueba.
Service virtualization: comportamiento más completo, incluidos fallos.
Usa cada uno según la pregunta. Simular todo crea falsa confianza; depender siempre del proveedor vuelve inestable el pipeline.
Timeout.
Respuesta parcial.
Duplicado.
Mensaje fuera de orden.
Reinicio a mitad de operación.
Base no disponible.
Disco lleno o cuota agotada.
Rollback de versión.
Restore desde backup.
La resiliencia solo existe si el mecanismo ha sido ejercitado.
Al fallar, una prueba debe mostrar:
Escenario ejecutado.
Datos relevantes.
Resultado esperado y obtenido.
Correlation ID o trace.
Estado final.
Artefactos de logs o métricas cuando aplica.
Capturar una enorme cantidad de logs sin estructura no facilita diagnóstico.
Automatiza cuando la comprobación:
Se repite.
Tiene oráculo claro.
Protege una capacidad importante.
Detecta regresión con suficiente estabilidad.
Mantén exploración humana cuando se necesita juicio, descubrimiento o percepción contextual.
Detecta tarde, cuesta diagnosticar y cubre pocas combinaciones.
Acopla las pruebas a refactors sin aumentar confianza.
La prueba termina verificando la expectativa del mock.
Muchos fallos reales no aparecen en ejecuciones lineales.
La cobertura señala código ejecutado, no riesgo demostrado.
El código nuevo puede funcionar mientras el cambio de datos rompe despliegue o recuperación.
Texto
Copiar Capacidad o riesgo:
Escenarios críticos:
Invariantes:
Niveles de prueba:
Datos:
Ambiente:
Dependencias:
Oráculo:
Evidencia:
Riesgo no cubierto:
Owner:
La estrategia nace de requisitos y riesgos, no del framework.
Cada prueba necesita un oráculo claro.
Distintos niveles responden preguntas distintas.
Testability es parte del diseño.
La operación completa la evidencia que no puede obtenerse antes del despliegue.
¿Por qué una prueba E2E no sustituye pruebas de reglas?
¿Qué diferencia existe entre contrato e integración?
¿Cómo probarías idempotencia después de un timeout?
¿Qué evidencia demostraría que un backup es útil?
Ver respuestas
Porque es más lenta, cubre menos combinaciones y diagnostica peor reglas específicas.
Contrato comprueba compatibilidad observable; integración demuestra colaboración real con infraestructura o proveedor.
Repite la misma operación con la misma clave y verifica resultado lógico único, efectos no duplicados y recuperación del primer intento.
Un restore ejecutado, medido y validado contra RTO, RPO e integridad esperada.
Gestión de cambios usa esta trazabilidad para modificar requisitos, contratos y datos sin perder compatibilidad ni evidencia.