Al terminar esta lección
- Producir la matriz de riesgo y los 12 casos de CU-01, cada uno con nivel, oráculo y criterio de aceptación asociado.
- Diseñar integration tests de rollback, idempotencia y concurrencia con oráculo de efectos y de ausencia de efectos.
- Diseñar dos contract tests y un plan de medición de NFR-01 que otra persona pueda repetir.
- Armar una RTM ejecutable y revisar el gate contra evidencia, dejando visibles los criterios que todavía no podés demostrar.
Práctica E10 · Demostrar CU-01
Situación. Diseñá la evidencia de aceptación del slice sin perseguir un porcentaje arbitrario de cobertura.
Entregá. matriz de riesgo; 12 casos; tests automatizados seleccionados; evidencia de ejecución; RTM mínima.
Está listo cuando. cada test defiende una propiedad; hay bordes, negativos, carrera y fallo externo; el oráculo mira efectos y ausencia de efectos; la evidencia se puede mostrar.
Solución comentada: Solución comentada E10. Comparala después de producir tu entrega.
Diseñá un plan de pruebas de CU-01 partiendo de riesgos. Para cada riesgo indicá nivel de prueba, preparación, estímulo, oráculo y evidencia.
- Escribí casos normal, frontera y negativo para crédito, cantidad y precio.
- Diseñá al menos tres integration tests: rollback, idempotencia y concurrencia real.
- Diseñá dos contract tests para una integración externa, incluyendo timeout o respuesta inválida.
- Elegí un NFR y explicá un entorno reproducible para medirlo. Incluí dataset, patrón de carga, duración y umbral.
- Tomá un bug ficticio de producción y escribí el test de regresión que conservarías después de corregirlo.
Ejecutá una suite real o simulada y prepará un paquete de evidencia: comandos, outputs relevantes, defectos encontrados, correcciones y trazabilidad a RN y AC.
Criterio de revisión. Podés decir "está probado" solo cuando sabés qué propiedad defendiste, con qué mecanismo, en qué entorno y qué evidencia podría repetir otra persona.
Antes de empezar
Necesitás tres cosas a mano: el caso de uso CU-01 (versión 4, la que ya validó el equipo con Laura y Marta), la matriz de 10.3 con T01 a T12 y el catálogo de criterios de aceptación. Este es el catálogo completo, que es la fuente para la columna AC de tu RTM:
- AC-01: happy path. Pedido confirmado, precio snapshot y reserva creada.
- AC-02: exactamente en el límite de crédito. Aprobado según RN-01.
- AC-03: un centavo sobre el límite. Rechazo sin efectos.
- AC-04: precio calculado en servidor (RN-03). El cliente no fija el precio.
- AC-05: SKU inválido. Sin pedido parcial ni reservas.
- AC-06: última unidad concurrente. Exactamente una reserva (RN-05).
- AC-07: reintento idempotente y conflicto por requestId (RN-06).
- AC-08: autorización de crédito. Pedido pendiente con registro de la solicitud (CU-02).
- AC-09: cantidad inválida y múltiplos de venta (RN-08).
- AC-10: falla de persistencia. Rollback completo.
- AC-11: handoff a preparación por outbox.
- AC-12: proveedor externo caído. El pedido permanece válido.
Si una AC no tiene caso en tu matriz, eso es un hueco y se declara. No se resuelve escribiendo una fila que no defiende nada.
Recorrido guiado
El taller tiene siete pasos. Cada uno tiene objetivo, tiempo sugerido, un ejemplo parcial ya trabajado y un ejercicio. Las pistas y las soluciones parciales aparecen plegadas. La solución completa vive en 14.4 y la comparás al final, no antes.
Paso 1 · Matriz de riesgo (15 minutos)
Objetivo. Listar los riesgos de CU-01 y, para cada uno, la propiedad que amenaza.
Ejemplo parcial. Así se ve una fila completa:
- Riesgo: última unidad concurrente en CBA.
- Propiedad: RN-05, la disponibilidad nunca es negativa.
- Mecanismo: lectura y escritura separadas de stock_level.
- Nivel: integration concurrente con dos conexiones y barrera.
- Oráculo: un Reserved, un InsufficientStock, disponibilidad final 0 y una sola fila en reservation.
Matriz de riesgo de CU-01
Listá al menos seis riesgos de CU-01. Para cada uno escribí la propiedad que amenaza, el mecanismo que podría romperla, el nivel que lo observa y el oráculo en una frase. Incluí al menos un riesgo de autoridad (precio o permiso), uno de fallo externo y uno de concurrencia.
Ver pistas para completar la matriz
- Para el riesgo de autoridad de precio, buscá el campo que el cliente puede enviar y que el servidor no debería aceptar. La RN-03 es el punto de partida.
- Para el riesgo de permisos, usá NFR-03 y la regla de cartera del vendedor. El oráculo incluye el registro del intento denegado.
- Para el riesgo de conflicto de idempotencia, la pregunta clave es qué pasa cuando el mismo requestId llega con otro contenido.
- Para el riesgo de persistencia, la pregunta es qué debe quedar después de una falla antes del commit.
Paso 2 · Los 12 casos con criterio y nivel (25 minutos)
Objetivo. Completar la matriz T01 a T12 y cruzarla con el catálogo de AC.
Ejemplo parcial. T03 tiene estos campos:
- Riesgo: un centavo sobre el límite.
- Preparación: cliente estándar, saldo de 480.000, límite de 500.000, pedido de 20.000,01.
- Resultado: rechazo por crédito, sin pedido, sin reserva, sin débito.
- Nivel: unit para la regla e integration para los efectos.
- AC: AC-03, con RN-01 como fuente.
Antes de escribir los casos, revisá qué criterios ya tienen una fila y cuáles quedan afuera.
¿Tiene caso en la matriz o es un hueco?
Para cada criterio de aceptación, elegí si la matriz de 10.3 ya tiene un caso que lo defienda.
AC-02: exactamente en el límite de crédito
AC-09: cantidad inválida y múltiplos de venta (RN-08)
AC-11: handoff a preparación por outbox
AC-12: proveedor externo caído
Cerrá o declará los huecos de AC
Para AC-09 y AC-11, escribí un caso nuevo (T13 y T14) con preparación, estímulo, oráculo, nivel y AC. Si no podés escribir el oráculo porque falta una regla, escribí el hueco como BLOCKED con el responsable y la pregunta concreta.
Ver solución parcial de T13 (AC-09)
T13 · AC-09 · Cantidad inválida y múltiplo de venta. Preparación: cliente estándar con stock suficiente, SKU que se vende solo por caja cerrada de 6 unidades. Estímulo: dos requests, uno con cantidad 0 y otro con cantidad 7. Oráculo: ambos rechazados por regla de unidad de venta (RN-08), sin pedido, sin reserva, sin registro de idempotencia. Nivel: unit para el cálculo del múltiplo e integration para la ausencia de efectos. Si la RN-08 no define qué pasa con una cantidad cero (rechazo o normalización), el caso queda BLOCKED con Marta y depósito como responsables.
Paso 3 · Integration tests: rollback, idempotencia y concurrencia (30 minutos)
Objetivo. Escribir tres integration tests que observen efectos y ausencia de efectos. Los tres son obligatorios para E10.
Ejemplo parcial. Este es el esqueleto del rollback (T09). Suponé que el helper fixture y la inyección de fallos están disponibles en tests/support. Lo que falta son los oráculos:
import { test } from "node:test";
import assert from "node:assert/strict";
import { fixture } from "../support/fixture";
test("T09: falla antes del commit no deja efectos (AC-10)", async () => {
const env = await fixture({
client: { id: "CLI-0042", tier: "estandar", balanceCents: 0, creditLimitCents: 50_000_000 },
stock: { "AX-442": { CBA: 10 } },
priceCents: { "AX-442": 1_190_000 },
});
env.failBefore("commit"); // inyecta el error justo antes del commit
await assert.rejects(env.createOrder({
requestId: "req-903",
sellerId: "SEL-01",
clientId: "CLI-0042",
deliveryMethod: "pickup",
items: [{ sku: "AX-442", quantity: 1 }],
}));
// completar: los oráculos de efectos ausentes
});
Un detalle que el caso todavía no resuelve: el canon no define qué kind de resultado devuelve una falla de persistencia. Aquí usamos assert.rejects y dejamos la decisión registrada. Si el API tiene que responder con un código concreto, esa decisión va a la matriz de 10.2 como contrato.
Oráculo de rollback (T09)
Completá el bloque de aserciones de T09. Listá cada efecto que no debe existir después de la falla y escribí la aserción que lo verifica. Después escribí los dos tests que faltan (T07 y T08 o T06) con la misma estructura: preparación, estímulo, oráculo.
Ver lista de aserciones de efectos ausentes para T09
Después de la falla inyectada antes del commit, la base tiene que cumplir todo esto:
- No existe ninguna fila en orders con el requestId, consultada en la base y no en la respuesta.
- No existen líneas de pedido asociadas a ese requestId.
- La cantidad de reservas de AX-442 en CBA es la misma que antes del intento.
- No hay fila en idempotency_keys para el requestId, para que un reintento sea un intento nuevo y no un conflicto.
- No hay entradas en outbox para el pedido que no existe.
- Si la auditoría registra intentos fallidos, hay exactamente una entrada del intento y ninguna de confirmación.
Paso 4 · Dos contract tests para una integración externa (20 minutos)
Objetivo. Diseñar dos contract tests sobre el proveedor fiscal: timeout con efecto posible y respuesta inválida.
Ejemplo parcial. El timeout después de procesar es el caso más útil para practicar. El proveedor emitió el comprobante y nuestro adaptador no lo sabe. El oráculo tiene que verificar que el sistema no vuelve a emitir a ciegas: consulta por la clave de idempotencia y converge al estado real. Los estados posibles y sus transiciones están en el diagrama de 10.2.
Dos contract tests del proveedor fiscal
Escribí dos contract tests. El primero simula timeout después de procesar: nombre, respuesta simulada del fake, número de emisiones esperado en el proveedor y estado local esperado. El segundo simula una respuesta sin identificador de comprobante: qué estado queda, qué no debe marcarse como emitido y qué alerta debe quedar visible. Indicá también la versión del stub o del sandbox que usás.
Ver pistas para los dos contract tests
- En el timeout después de procesar, el fake tiene que registrar cuántas veces se emitió. El oráculo cuenta emisiones en el proveedor, no llamadas del worker.
- La consulta por clave de idempotencia es lo que permite converger. Si el proveedor no ofrece esa semántica, el test no puede asumirla: eso es una pregunta abierta para el contrato, y se escribe como tal.
- En la respuesta sin identificador, el estado local no puede ser emitido. Pensá qué debería ver una persona de administración en ese caso.
- Anotá en el test la versión del stub. Si el proveedor cambia su contrato, esa versión es la que tenés que actualizar.
Paso 5 · Plan de medición de NFR-01 (15 minutos)
Objetivo. Escribir un plan de medición que otra persona pueda repetir sin preguntarte nada.
Ejemplo parcial. NFR-01 pide que el p95 del registro de pedido, excluyendo el servicio fiscal, sea menor a 800 ms con 30 usuarios concurrentes. El plan tiene que dejar escrito todo lo que cambia el resultado:
nfr: NFR-01
escenario: registro de pedido, p95 menor a 800 ms con 30 usuarios concurrentes
excluye: servicio fiscal (no participa de CU-01 en el primer release)
entorno:
base: PostgreSQL 16 con la misma configuración de aislamiento que producción
aplicacion: una instancia del nodo de aplicación y el worker, mismo build del release candidate
hardware: completar
dataset:
skus: 8000
vendedores: 25
clientes: 600 # completar con el criterio de muestreo
stock_inicial: completar # que no genere rechazos durante la medición
carga:
rampa: de 0 a 30 usuarios en 2 minutos
estable: 30 usuarios durante 10 minutos
pausa_entre_pedidos: completar
calentamiento_descartado: completar
medicion:
metrica: p95 y p99 de la duracion de POST /orders hasta la respuesta
corridas: completar
criterio:
exito: completar
evidencia: completar
Plan de medición de NFR-01
Completá los campos marcados. Justificá el tiempo de pausa entre pedidos a partir del flujo real de un vendedor, la duración del calentamiento, la cantidad de corridas y el criterio de éxito. Explicá qué pasa con los 422 y los 409 en el cálculo del p95.
Ver criterios para completar el plan
- Pausa entre pedidos: un vendedor carga un pedido, revisa precios y crédito, y vuelve a cargar. Un valor de decenas de segundos representa mejor la carga real que un bucle continuo. Justificalo con el flujo que se observó en la práctica, no con una cifra general.
- Calentamiento: los primeros minutos de la rampa, descartados del cálculo, para que las cachés y las conexiones del pool estén en régimen.
- Corridas: al menos tres, con la base restaurada al mismo estado antes de cada una. Guardá la cantidad y los resultados individuales.
- Criterio de éxito: p95 menor a 800 ms en todas las corridas y cero errores 5xx. Los 422 y 409 se cuentan aparte y se reportan, porque son respuestas de negocio válidas.
Paso 6 · Test de regresión sobre un bug ficticio (10 minutos)
Objetivo. Convertir un defecto ya corregido en una barrera automática.
Bug ficticio. En la versión 2.3 de la app de prueba, el endpoint de pedidos aplicaba un campo discountPercentage que el cliente enviaba en el request, y lo restaba del total antes de calcular el crédito. El defecto ya fue corregido. Ahora tenés que decidir qué test conservar.
Test de regresión del descuento enviado por el cliente
Escribí el test de regresión que conservarías después de la corrección. Decidí su nivel y justificalo: si la causa es una regla pura de cálculo, unit; si el total depende del snapshot persistido o de la transacción, integration. Incluí el oráculo y explicá qué bug volvería a aparecer si alguien quita la corrección.
Ver solución parcial de la regresión
El defecto afectaba lo persistido, así que el test de regresión es de integración. El oráculo tiene que cubrir dos cosas: que el campo enviado por el cliente no altera el total guardado y que el crédito se evalúa sobre el total del servidor. Un test de unit sobre la función de cálculo no detectaría que el valor llegó a la base por otro camino. Si alguien vuelve a leer el descuento del request, este test se pone rojo en la aserción del total persistido.
Paso 7 · RTM ejecutable y paquete de evidencia (20 minutos)
Objetivo. Convertir la matriz en un artefacto que el código pueda verificar, y reunir la evidencia.
Una RTM en un documento de texto envejece en silencio. Una RTM ejecutable tiene su versión en el repositorio y un test que falla cuando una AC no tiene test asociado. Así, cada hueco queda visible en la salida de la suite.
cu: CU-01
rtm:
- ac: AC-06
rn: RN-05
riesgo: última unidad concurrente
nivel: integration concurrente
tests:
- tests/integration/stock-race.test.ts
evidencia: evidencia/stock-race-salida.txt
estado: probado
- ac: AC-03
rn: RN-01
riesgo: un centavo sobre el límite
nivel: unit + integration
tests:
- tests/integration/credit-boundary.test.ts
evidencia: evidencia/credit-boundary-salida.txt
estado: probado
- ac: AC-09
rn: RN-08
riesgo: cantidad inválida y múltiplos de venta
nivel: unit + integration
tests: []
estado: pendiente
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { parse } from "yaml";
const rtm = parse(readFileSync("docs/rtm-cu-01.yaml", "utf8")) as {
rtm: Array<{ ac: string; tests: string[]; estado: string }>;
};
for (const row of rtm.rtm) {
test(`${row.ac} tiene al menos un test o está declarado pendiente`, () => {
if (row.estado === "pendiente") return; // el hueco es visible en la RTM, no oculto
assert.ok(row.tests.length > 0, `${row.ac} no tiene tests asociados`);
});
}
Esta versión deja pasar AC-09 porque está marcada como pendiente, y eso es correcto: el hueco existe y queda declarado. Si alguien cambia el estado a "probado" sin agregar un test, la suite se pone roja. Para AC-11, si la fila no existe en absoluto, el test no la ve: por eso la RTM tiene que listar todas las AC del catálogo, incluso las pendientes.
Antes de dar por buena la evidencia, ejecutá la secuencia en este orden y guardá cada salida:
Ordená la secuencia de evidencia
Dejá el orden en el que conviene ejecutar y guardar la evidencia de CU-01.
- Posición 1 de 5: Recién entonces revisar el gate y marcar los criterios
- Posición 2 de 5: Guardar las salidas junto con el commit, el entorno y el dataset
- Posición 3 de 5: Ejecutar la prueba de la prueba: T06 contra la versión ingenua y verificar que queda roja
- Posición 4 de 5: Ejecutar el test de la RTM y revisar los huecos declarados
- Posición 5 de 5: Ejecutar la suite focal contra PostgreSQL con la configuración de producción
Cuando armes el paquete de evidencia, incluí el comando exacto, el commit, la versión del entorno, la cantidad de ejecuciones de las pruebas concurrentes y la salida textual. El paquete tiene que permitir que otra persona reproduzca el resultado sin preguntarte nada. Si la evidencia depende de que vos la expliques, todavía no está lista.
Hoja de entrega 10 · completá antes de pasar el gate
Este bloque no se completa de memoria. Volvé a tus artefactos, a la evidencia y a los resultados de ejecución. El objetivo es hacer explícito qué vas a defender frente a un mentor o un cliente.
- Artefacto que entrego y versión:
- Tres evidencias que sostienen mis decisiones:
- Decisiones que tomé yo y que un agente no podía tomar:
- Desconocidos o riesgos que siguen abiertos:
- Feedback o propuesta de IA que rechacé, y por qué:
- Qué tendría que ocurrir para que yo mismo cambie esta conclusión:
- Revisión de calidad: qué corregí antes de enviar o presentar:
- Firma del mentor o reviewer, o referencia del feedback:
Gate 10 · podés afirmar que el comportamiento está probado
Un gate no es una formalidad. Si una condición crítica no se cumple, el trabajo correcto es hacer visible la incertidumbre y volver a buscar evidencia. Un criterio pendiente con su motivo es un resultado válido de E10. Un criterio marcado sin evidencia no lo es.
Criterios de revisión
Revisá tus artefactos antes de marcar cada criterio. El checklist registra tu autoevaluación; la aprobación requiere revisar la evidencia.
Guardá tu entrega
Completá la entrega 10, adjuntá evidencia y registrá los criterios del gate que todavía no podés demostrar.
Cuando termines tu entrega, compará tu razonamiento con la solución comentada E10. No hay una única respuesta correcta: la comparación busca fuentes, decisiones y evidencia.
Errores frecuentes
- Síntoma: la matriz tiene doce filas, todas verdes, y la RTM tiene menos AC que el catálogo. Causa: la matriz se armó para llenar la tabla y no desde el catálogo de criterios. Corrección: partí del catálogo de AC y declará los huecos en lugar de omitirlos.
- Síntoma: el test de concurrencia pasa en la primera corrida y nadie guardó la cantidad de corridas. Causa: la evidencia depende de una ejecución y no de una secuencia. Corrección: repetí la prueba, guardá cada resultado y anotá el número en el paquete de evidencia.
- Síntoma: el contract test confirma el formato de la respuesta feliz y nada más. Causa: el fake solo modela el éxito. Corrección: agregá al menos timeout antes y después de procesar, rechazo permanente y respuesta sin identificador.
- Síntoma: el plan de NFR tiene el umbral pero no el dataset ni el calentamiento. Causa: se escribió el criterio antes de definir cómo medir. Corrección: completá entorno, dataset, carga, calentamiento, duración, percentil y corridas antes de medir, y no ajustes el umbral después.
- Síntoma: el gate se marca completo porque el agente informó que todo pasa. Causa: la evidencia es la afirmación del agente y no una salida guardada. Corrección: ejecutá la secuencia de evidencia y revisá la salida con tus propios ojos antes de marcar cada criterio.
Para llevarte
- La matriz sale del catálogo de criterios de aceptación. Los huecos se declaran, no se disimulan con filas que no defienden nada.
- Cada integration test de E10 observa efectos y ausencia de efectos en la base real. Si el oráculo solo mira el status, el test no está terminado.
- Los contract tests defienden la traducción a un tercero, sobre todo el timeout con efecto posible y la respuesta sin identificador.
- Un NFR se mide con un plan que otra persona pueda repetir: entorno, dataset, carga, calentamiento, corridas y criterio escritos antes de medir.
- La RTM ejecutable convierte la trazabilidad en algo que el código verifica. Un hueco declarado es información; un hueco escondido es un riesgo.
- Un criterio del gate se marca solo con evidencia guardada. Lo que no podés demostrar se deja visible con su motivo.
¿Cómo valoras el contenido técnico de esta lección?
Tu feedback nos ayuda a mejorar los laboratorios prácticos de Eje Z.