El costo de migrar a ciegas

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.

La idea en una línea: antes de tocar código que no entendés al 100 %, le sacás una foto a lo que hace hoy. Esa foto es el contrato que la migración tiene que respetar.
Por qué pasó

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.

Lo que ves

Compila. Los happy paths funcionan. La demo sale bien.

Lo que no ves

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.

Definición

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.

output original
{
  "neto": 826.45,
  "iva": 173.55,
  "promo": true,
  "pago": "COMPLETO"
}
DIFF
LIMPIO
output migrado
{
  "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.

La distinción clave

El unit test hereda tus agujeros.
El golden no.

Esta diferencia es la raíz de tu problema:

Unit test

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.

Golden test

No requiere entender nada. Inputs → outputs → congelar. Pina el comportamiento aunque no sepas por qué el código hace lo que hace.

output original
descuento = 0.15
total = 850.00
REGRESIÓN
DETECTADA
output migrado
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.

De dónde viene

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.

El proceso que faltó

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).

Regla de oro del paso 4: un diff nunca se ignora. O es una regresión que arreglás, o es un cambio intencional que aprobás actualizando el golden a mano. No hay tercera opción.
Dónde falla un golden suite

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:

1 · Determinismo

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.

2 · Cobertura del corpus

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.

Enemigo 1 · a normalizar

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-AR explícito.
Serialización determinística: JSON con claves ordenadas, floats con precisión fija, encoding explícito. La foto tiene que salir idéntica byte a byte cuando el comportamiento es idéntico.
Enemigo 2 · el corpus

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.

En tu terreno

Dónde encaja con lo que ya hacés.

Shim, not rewrite

El golden suite es la spec ejecutable del contrato externo que querés preservar. Es la definición operativa de "no rompí nada".

Spec-loop

Encaja como spec de facto: el comportamiento capturado alimenta el loop, y el diff limpio es una condición verificable, no una opinión.

Gate en /qa

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.

Herramientas

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.

Para que no vuelva a pasar

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.

Checklist express: Capturé output real → lo serialicé determinístico (locale 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.
01 / 11