Testabilidad y estrategia de pruebas arquitectónicas
Explica cómo diseñar testabilidad y una estrategia de pruebas basada en riesgos para reglas, contratos, persistencia, concurrencia, resiliencia y arquitectura.
Última actualización
Actualizada
Nivel
Aplicación
La testabilidad no es una propiedad exclusiva del código de pruebas. Es una cualidad arquitectónica: el sistema debe permitir controlar entradas, observar resultados y comprobar garantías sin depender siempre del entorno completo ni ocultar fallos detrás de mocks.
Una estrategia de pruebas arquitectónicas busca evidencia sobre distintos riesgos.
Texto
reglas locales
→ pruebas unitarias
integración con mecanismos reales
→ pruebas de integración
límites entre componentes
→ contract tests
journeys críticos
→ end-to-end
atributos de calidad
→ carga, resiliencia, seguridad y recuperación
reglas estructurales
→ fitness functions y pruebas de arquitectura
No existe una única pirámide válida para todos los sistemas. La mezcla depende del dominio, los fallos costosos y la topología. Un sistema de pagos necesita más evidencia sobre idempotencia y reconciliación que una landing page. Un monolito modular necesita pruebas de límites internos aunque no use red.
¿Qué costo tiene un falso positivo o un falso negativo?
Texto
riesgo: vender más stock del disponible
frontera: dominio + transacción de base de datos
prueba: solicitudes concurrentes sobre la última unidad
resultado: una reserva exitosa, las demás rechazadas
Una prueba unitaria del método decreaseStock no demuestra que la persistencia proteja concurrencia. Una prueba end-to-end completa puede demostrarlo, pero quizá sea lenta y difícil de diagnosticar. La estrategia correcta combina pruebas en la frontera donde vive el riesgo.
El caso de uso recibe dependencias explícitas. Una prueba puede usar fakes para explorar reglas; las pruebas de adaptadores comprueban base de datos e integraciones reales.
La abstracción se justifica porque tiempo, persistencia e inventario son fronteras con comportamiento variable. No es necesario crear interfaces para funciones puras que no necesitan sustitución.
it("rechaza una devolución superior al monto pagado",()=>{const sale = Sale.paid({ total:100_000});expect(()=> sale.refund({ amount:120_000})).toThrow(RefundExceedsPaymentError);});
La prueba comprueba una regla sin infraestructura. No demuestra transacciones, autorización ni persistencia.
Mockear cada colaboración hace que las pruebas reproduzcan el código en lugar del comportamiento. Un refactor rompe tests aunque el contrato no cambie.
Prefiere:
Objetos reales para lógica local.
Fakes para puertos estables.
Mocks o spies cuando la interacción sea parte del contrato.
Los consumidores expresan las partes que utilizan y el proveedor verifica que no las rompe.
Los contract tests reducen la necesidad de desplegar todo el sistema para detectar incompatibilidad. No demuestran comportamiento completo del negocio ni disponibilidad de red.
Evita fixtures gigantes sin intención. Un builder puede expresar únicamente lo relevante:
TypeScript
const sale =aSale().forBusiness("business-a").paidWithTotal(100_000).build();
La producción también contiene datos inesperados. Las migraciones y parsers necesitan pruebas con muestras reales anonimizadas o generadas a partir de sus características.
Fitness functions y gobierno automatizado convierte las restricciones arquitectónicas más importantes en controles continuos dentro del flujo de entrega.