La migración corrió,
pero se perdió lógica.
Cuando modernizás un stack, el riesgo grande no es que reviente ruidosamente —eso se ve—. Es la pérdida silenciosa de comportamiento: una rama de negocio que el agente "simplificó", un edge case que no reconoció, un side effect que se evaporó en la traducción.
El golden test es lo único que grita cuando eso pasa. Este es el porqué, el cómo, y dónde encaja con lo que ya venís construyendo.
El agente migra lo que entiende.
Un LLM traduce un stack reproduciendo el comportamiento que logró modelar. Lo que no entendió, lo aproxima o directamente lo descarta —y lo hace sin avisar, porque para él el resultado "parece razonable".
Ahí está la trampa: la regresión no rompe un test, no tira una excepción, no dispara un log de error. El código nuevo compila, arranca y responde. Simplemente responde distinto en los caminos que nadie estaba mirando.
Compila. Los happy paths funcionan. La demo sale bien.
El descuento por PROMOCIÓN que ya no se aplica. El redondeo de IVA corrido un centavo. El PAGO que no completa en un caso raro.
En dominio fiscal esto no es cosmético: un centavo en la descomposición de IVA o un CAE que no numera es un problema de compliance, no un bug de UI.
Una foto del comportamiento real.
Un golden test —también golden master, characterization test o approval test— no verifica lo que el código debería hacer según la spec. Verifica lo que el código realmente hace hoy: con bugs, quirks y comportamientos raros incluidos.
Le tirás un input, capturás el output observable y lo congelás como referencia (el golden). Después de migrar, corrés el mismo input por el código nuevo y hacés diff contra esa foto.
{
"neto": 826.45,
"iva": 173.55,
"promo": true,
"pago": "COMPLETO"
}
LIMPIO
{
"neto": 826.45,
"iva": 173.55,
"promo": true,
"pago": "COMPLETO"
}
Si coincide, preservaste el comportamiento. Si no, encontraste la regresión —en el instante en que aparece, no tres semanas después en producción.
El unit test hereda tus agujeros.
El golden no.
Esta diferencia es la raíz de tu problema:
Codifica tu entendimiento del código. Si vos —o el agente— no entendiste una rama, tampoco escribís el test que la cubre. El agujero de conocimiento se propaga al agujero de testing.
No requiere entender nada. Inputs → outputs → congelar. Pina el comportamiento aunque no sepas por qué el código hace lo que hace.
descuento = 0.15 total = 850.00
DETECTADA
descuento = 0.00 total = 1000.00
Nadie escribió un unit test para el descuento porque nadie lo tenía en el radar. El golden lo agarra igual, porque no depende de que alguien lo tuviera en el radar.
Caracterizar antes de tocar.
La técnica viene de Michael Feathers (Working Effectively with Legacy Code). La premisa: en código que no entendés del todo, primero lo caracterizás —capturás su comportamiento actual como red de contención— y recién después lo tocás.
No estás testeando lo correcto.
Estás fijando lo que hay, para poder cambiar el cómo sin cambiar el qué.
Es exactamente el mismo espíritu de tu estrategia "shim, not rewrite": preservás el contrato externo observable y modernizás por dentro. El golden suite es la versión ejecutable de ese contrato.
Cuatro pasos, en este orden.
El orden importa: los primeros dos pasos ocurren antes de tocar una sola línea de la migración.
Capturar
Corrés el código original con un corpus amplio de inputs reales. Registrás todo el output observable: returns, escrituras a DB, archivos emitidos, respuestas HTTP.
Congelar
Commiteás esos fixtures como golden. Esa es tu spec ejecutable de facto. Nadie los edita a mano.
Migrar
Recién ahora hacés la modernización del stack, tranquilo, con la red puesta.
Verificar
Mismos inputs por el código nuevo, diff contra golden. Diferencia → regresión real o cambio esperado (que aprobás explícitamente).
Solo dos cosas lo arruinan.
Un golden suite mal armado es peor que ninguno: genera ruido, lo empezás a ignorar, y perdés la red justo cuando la necesitás. Los dos puntos de falla:
Si el output depende de cosas no determinísticas, el diff da falsos positivos constantes y terminás ignorándolo. Es la parte difícil.
El golden vale exactamente lo que valen los inputs que le metiste. Si la rama perdida nunca se ejercitó, tampoco la habría pintado.
Las dos próximas secciones son la checklist concreta para cada una.
Todo lo que cambia entre corridas.
Antes de serializar el output, controlás o normalizás todas estas fuentes de variación —si no, el diff miente:
- Timestamps y fechas — congelás el reloj o los reemplazás por un placeholder estable.
- Random / seeds — seed fijo, o mock del generador.
- Orden de iteración de maps y sets — ordenás las claves antes de serializar.
- GUIDs y auto-increment de DB — los normalizás o los excluís del snapshot.
- Concurrencia — el orden entre threads no puede filtrarse al output.
- Locale — separador decimal, formato de fecha, cultura. Un golden armado en tu máquina y corrido en otra con locale distinto se rompe entero. Fijás
es-ARexplícito.
Datos de producción reales.
Un golden test solo protege los caminos que ejercitaste. Lo que se te perdió probablemente fue un camino poco frecuente —justo el tipo de cosa que un corpus armado a mano no cubre y uno sintético "prolijo" nunca toca.
La mejor fuente, en orden
- Muestras de producción reales (anonimizadas si hace falta) — capturan la distribución verdadera, incluidos los casos que ni sabías que existían.
- Edge cases construidos a mano — los límites que sí conocés: montos en cero, promociones combinadas, contingencia CAEA, tickets con impuestos internos dentro de la base.
- Casos que ya te rompieron antes — cada bug histórico es un input de oro para el corpus.
El golden test vale exactamente
lo que vale su corpus.
Dónde encaja con lo que ya hacés.
El golden suite es la spec ejecutable del contrato externo que querés preservar. Es la definición operativa de "no rompí nada".
Encaja como spec de facto: el comportamiento capturado alimenta el loop, y el diff limpio es una condición verificable, no una opinión.
Metés "golden diff limpio" como condición dura antes de dar la migración por buena, encadenado en el orchestrator con el resto de los gates.
En Java, ApprovalTests.Java te da capturar / aprobar / diff sin escribir el harness. Para I/O de sistema, muchas veces conviene un dir de fixtures + runner propio que normalice y diffee.
La regla mental.
Antes de modernizar cualquier cosa
que no entendés al 100 %,
primero pinás su comportamiento actual
con golden tests.
Recién ahí la migración tiene red.
es-AR incluido) → el corpus tiene datos de prod y edge cases → congelé el golden → migré → diff limpio como gate en /qa. Ningún diff se ignora: se arregla o se aprueba.