Módulo 2: Entender y fijar el legacy
Caracterizar por muestreo y el golden master
Descripción
Hasta aquí fijamos el comportamiento con casos elegidos a mano: tres ítems, cinco, un puñado cuidadosamente escogido para cubrir los bordes. Eso funciona cuando puedes enumerar los casos importantes con la cabeza. Pero el catálogo real de Mercado no se deja enumerar: cada precio depende de un precio unitario cualquiera, una cantidad cualquiera, una de varias categorías, un cupón que puede estar o no, y una lealtad que puede ser gold o no. Son miles de combinaciones, y las interacciones raras —las que rompen— se esconden justo en combinaciones que a nadie se le ocurriría escribir a mano. Elegir seis casos deja hoyos; y como vimos en la lección 3, una red con hoyos en los bordes da confianza donde no deberías tenerla. Esta lección resuelve el problema de escala con dos ideas que van juntas: muestrear el espacio de entradas y grabar un golden master.
Muestrear es generar muchas entradas de forma sistemática —con una semilla fija, para que sean siempre las mismas— en vez de escribirlas una por una. En vez de seis ítems pensados, generas quinientos ítems pseudoaleatorios pero reproducibles, que barren el espacio de combinaciones mucho mejor de lo que tú lo harías a mano. El golden master es la foto de las salidas del legacy para todas esas entradas: corres el legacy sobre los quinientos ítems, guardas los quinientos precios, y ese registro se vuelve la referencia canónica del "cómo funciona hoy". A partir de ahí, cualquier reimplementación candidata se compara contra el golden master en un parallel-run: corres la candidata sobre las mismas entradas y ves, entrada por entrada, dónde su salida difiere de la foto. Lo poderoso es que no necesitas saber de antemano dónde está la rareza: el diff te lleva a ella. Como vas a ver, quinientas comparaciones acorralan la rareza gold sin que nadie hubiera apuntado hacia los clientes gold.
Conexión con el módulo. La lección 3 te enseñó a fijar un caso; esta escala esa técnica a cientos, que es lo que un legacy real exige. La 4 te dio los seams que hacen posible correr el legacy de forma repetible (sin ellos, no podrías generar un golden master determinista de una función que consulta el reloj). Aquí juntas todo: seams para volverlo determinista, muestreo para cubrir el espacio, golden master para fijar la foto completa. La lección 6 tomará la discrepancia que este método encuentra —la rareza gold— y preguntará qué hacer con ella. La frontera con Testing se mantiene: el golden master es una técnica de caracterización para migrar, no una discusión sobre property-based testing o fuzzing en abstracto. Y esto es golden master (caracterización) y el parallel run de Sam Newman, en su sentido técnico exacto.
Una analogía: el master dorado de la fábrica de discos
El nombre "golden master" no es una metáfora inventada por programadores: viene de la manufactura de medios. Cuando se prensaba un disco o se replicaba una película, primero se producía una copia maestra aprobada —el golden master, literalmente la cinta o el disco de referencia, a veces bañado en oro por durabilidad—. Esa copia maestra era la verdad: todas las copias que salían de la fábrica se comparaban contra ella. Si una copia sonaba distinto del master, aunque fuera un chasquido en el segundo 47 que nadie habría notado a oído, se rechazaba —no porque el master fuera "perfecto" (podía tener el chasquido también), sino porque la copia difería del master aprobado—. El control de calidad no preguntaba "¿esta copia es buena?"; preguntaba "¿esta copia es idéntica al master?". Y como comparar millones de copias contra un master a oído humano es imposible, la fábrica no escuchaba cada segundo de cada copia: muestreaba —tomaba puntos de control a lo largo de la grabación— y comparaba solo esos.
El golden master del software es exactamente eso. Grabas la salida del legacy para un montón de entradas —esa es tu copia maestra aprobada, el "cómo funciona hoy", chasquidos incluidos—. Después, cada reimplementación candidata es una "copia" que se compara contra el master: si difiere en algún punto, se rechaza (o al menos, se marca para decidir) —no porque el master sea correcto, sino porque la candidata no reproduce el master—. Y como no puedes enumerar todas las entradas posibles, muestreas: generas quinientas entradas representativas y comparas esas. El paralelo es tan directo que hasta el nombre se conservó. Cuando en la próxima sección grabes 500 precios del legacy y compares una candidata contra ellos, estás corriendo la línea de control de calidad de la fábrica de discos, aplicada a una función de precio.
Ejemplo trabajado: 500 entradas y la rareza acorralada
Vamos a muestrear 500 ítems del catálogo con semilla fija, grabar el golden master del legacy, y correr un parallel-run contra una reimplementación candidata —la de siempre: la que aplica la lealtad gold antes del impuesto en vez de después—. La clave del ejemplo: no le vamos a decir al código dónde está la diferencia. Vamos a dejar que el diff la encuentre.
import math
import random
TAX_RATE = 0.16
CATEGORY_DISCOUNT = {"electronics": 0.05, "books": 0.10, "toys": 0.08, "grocery": 0.00}
BULK_MIN_QTY, BULK_DISCOUNT, GOLD_LOYALTY_DISCOUNT = 10, 0.05, 0.03
def _floor_cents(a):
return math.floor(a * 100) / 100
def legacy_catalog_price(item):
subtotal = _floor_cents(item["unit_price"] * item["quantity"])
price = _floor_cents(subtotal * (1 - CATEGORY_DISCOUNT.get(item["category"], 0.0)))
if item["quantity"] >= BULK_MIN_QTY:
price = _floor_cents(price * (1 - BULK_DISCOUNT))
price = _floor_cents(price * (1 - item.get("coupon_percent", 0.0)))
price = _floor_cents(price * (1 + TAX_RATE))
if item.get("loyalty") == "gold":
price = _floor_cents(price * (1 - GOLD_LOYALTY_DISCOUNT))
return price
def candidate_catalog_price(item):
# Reimplementacion candidata: identica salvo que aplica gold ANTES del impuesto.
subtotal = _floor_cents(item["unit_price"] * item["quantity"])
price = _floor_cents(subtotal * (1 - CATEGORY_DISCOUNT.get(item["category"], 0.0)))
if item["quantity"] >= BULK_MIN_QTY:
price = _floor_cents(price * (1 - BULK_DISCOUNT))
price = _floor_cents(price * (1 - item.get("coupon_percent", 0.0)))
if item.get("loyalty") == "gold":
price = _floor_cents(price * (1 - GOLD_LOYALTY_DISCOUNT))
price = _floor_cents(price * (1 + TAX_RATE))
return price
def generate_inputs(n, seed=7):
# Muestreo del espacio de entradas con semilla fija: SIEMPRE los mismos n items.
rng = random.Random(seed)
cats = list(CATEGORY_DISCOUNT)
items = []
for i in range(n):
items.append({
"sku": f"GM-{i:04d}",
"unit_price": round(rng.uniform(1, 500), 2),
"quantity": rng.randint(1, 20),
"category": rng.choice(cats),
"coupon_percent": rng.choice([0.0, 0.0, 0.10, 0.15, 0.20]),
"loyalty": rng.choice([None, None, None, "gold"]),
})
return items
N = 500
inputs = generate_inputs(N)
# 1) GRABAR el golden master: la salida del legacy para las N entradas.
golden_master = [(it["sku"], legacy_catalog_price(it)) for it in inputs]
print(f"golden_master grabado: {len(golden_master)} entradas del legacy.")
print("Muestra (primeras 4):")
for sku, price in golden_master[:4]:
print(f" {sku}: {price}")
print()
# 2) COMPARAR una reimplementacion candidata contra el golden master.
print("parallel_run: candidate vs golden_master")
diffs = []
for (sku, expected), it in zip(golden_master, inputs):
actual = candidate_catalog_price(it)
if actual != expected:
diffs.append((sku, expected, actual, it.get("loyalty")))
print(f" {N - len(diffs)} coinciden, {len(diffs)} difieren.")
gold_diffs = [d for d in diffs if d[3] == "gold"]
nongold_diffs = [d for d in diffs if d[3] != "gold"]
print(f" de las {len(diffs)} discrepancias: {len(gold_diffs)} son de clientes "
f"gold, {len(nongold_diffs)} son de clientes no-gold.")
print(" Primeras 5 discrepancias:")
for sku, expected, actual, loyalty in diffs[:5]:
print(f" {sku} legacy={expected:<8} candidate={actual:<8} loyalty={loyalty}")
print()
print("El golden master no sabia que la rareza era 'gold despues del impuesto';")
print("solo comparo 500 salidas y senialo EXACTAMENTE donde el candidato difiere.")
print("El 100% de las discrepancias cae en clientes gold: la rareza quedo acorralada.")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
golden_master grabado: 500 entradas del legacy.
Muestra (primeras 4):
GM-0000: 943.02
GM-0001: 1402.95
GM-0002: 3404.11
GM-0003: 1434.38
parallel_run: candidate vs golden_master
458 coinciden, 42 difieren.
de las 42 discrepancias: 42 son de clientes gold, 0 son de clientes no-gold.
Primeras 5 discrepancias:
GM-0016 legacy=1751.88 candidate=1751.89 loyalty=gold
GM-0028 legacy=6993.54 candidate=6993.53 loyalty=gold
GM-0029 legacy=2240.17 candidate=2240.18 loyalty=gold
GM-0042 legacy=2399.31 candidate=2399.3 loyalty=gold
GM-0046 legacy=827.51 candidate=827.5 loyalty=gold
El golden master no sabia que la rareza era 'gold despues del impuesto';
solo comparo 500 salidas y senialo EXACTAMENTE donde el candidato difiere.
El 100% de las discrepancias cae en clientes gold: la rareza quedo acorralada.
Lee el resultado del parallel-run con calma, porque encierra el argumento de por qué el golden master vale la pena. De las 500 entradas, 458 coinciden y 42 difieren. Y aquí está lo notable: las 42 discrepancias, sin excepción, son de clientes gold —cero de clientes no-gold—. El golden master no tenía ni idea de que la diferencia entre el legacy y la candidata era "gold antes vs después del impuesto". No sabía nada del dominio. Solo hizo una cosa boba y poderosa: comparó 500 pares de números y señaló los 42 que no cuadraban. Y esos 42 apuntaron, con precisión de láser, exactamente al segmento afectado. La rareza quedó acorralada sin que nadie hubiera dirigido la búsqueda hacia ella.
Piensa en lo que eso te da. Si hubieras caracterizado a mano y por casualidad no hubieras incluido ningún cliente gold, la candidata habría pasado tu red en verde y la regresión se habría ido a producción —justo lo que la lección 3 advertía sobre el camino feliz—. El muestreo garantizó que hubiera clientes gold entre las 500 entradas (de hecho, muchos), así que la rareza tenía que aparecer en el diff. No dependiste de tu intuición para saber dónde mirar; el barrido lo cubrió por ti.
Fíjate también en un detalle fino de las cinco discrepancias que imprime: la diferencia va en las dos direcciones —a veces el candidato cobra un centavo más (GM-0016: 1751.88 → 1751.89), a veces un centavo menos (GM-0042: 2399.31 → 2399.30)—. Esto revela que el efecto de mover el descuento gold respecto al impuesto, combinado con el redondeo hacia abajo en cada paso, no es un sesgo uniforme: empuja el último centavo hacia arriba o hacia abajo según cómo caigan los números de cada ítem. Es exactamente la clase de comportamiento sutil y dependiente de los datos que jamás caracterizarías correctamente con seis casos a mano —necesitas volumen para verlo—.
Y un último punto que el resultado enseña sin decirlo: no todos los clientes gold difieren. Hay muchos más de 42 clientes gold entre las 500 entradas, pero solo 42 producen una diferencia; en el resto, el redondeo absorbe el cambio y el precio final coincide. O sea, la rareza no se manifiesta en todos los casos gold, solo en algunos, dependiendo de los centavos. Esto hace la regresión todavía más escurridiza para una red hecha a mano: aunque hubieras probado un cliente gold, podrías haber caído justo en uno de los que no difieren, y la red te habría dado verde en falso. El golden master, con sus 500 casos, no te deja ese margen de suerte.
Profundización: cómo se graba y se usa un golden master en la práctica
El ejemplo mantuvo el golden master en memoria por simplicidad, pero en un proyecto real lo grabas en un archivo —el "master aprobado" que vive junto al código—. El flujo tiene tres momentos, y conviene tenerlos claros:
- Grabar (una vez). Generas las entradas de muestreo (con semilla fija) y corres el legacy sobre ellas, guardando cada salida. El resultado —las N entradas y sus N salidas— se serializa a un archivo (un
.json, un.txt, lo que sea legible y diffeable) y se versiona junto al código. Ese archivo es el golden master. Se graba una sola vez, cuando el legacy todavía está intacto: es la foto del "antes". - Comparar (cada vez que cambias). Antes de mergear un cambio, corres la implementación actual sobre las mismas entradas y comparas su salida contra el archivo. Si todo coincide, verde: no cambiaste comportamiento. Si algo difiere, rojo: te muestra exactamente qué entradas cambiaron y cómo. Esto es el parallel-run, y es lo que hicimos en el ejemplo.
- Aprobar un cambio deliberado (raras veces). Si el diff muestra cambios que sí querías —porque el negocio decidió que ahora el precio se calcula distinto—, regeneras el golden master (lo "re-apruebas") y versionas el nuevo archivo. Esto tiene que ser un acto consciente y revisado, nunca automático: re-aprobar el master a la ligera es como firmar el acta de estado sin mirar. El diff que aparece en ese momento es la lista literal de lo que estás cambiando en producción —hay que leerlo—.
Dos cuidados prácticos. Primero, la reproducibilidad es sagrada: si las entradas de muestreo cambian de una corrida a otra (semilla no fija, orden no determinista), el golden master es inútil, porque comparas contra una foto de otra escena. Por eso fijamos random.Random(seed=7) —siempre genera las mismas 500 entradas—. Segundo, el tamaño y la cobertura importan más que la aleatoriedad pura: 500 entradas uniformes están bien, pero un buen muestreo se asegura de golpear los bordes a propósito —cantidades en el umbral de volumen, cupones al límite, todas las categorías, clientes gold en proporción suficiente—. El muestreo puramente aleatorio puede, por mala suerte, sub-representar un caso raro; combinar aleatorio con algunos casos borde forzados es lo más robusto. La idea es la de la fábrica: los puntos de control del muestreo tienen que caer donde el sonido es difícil, no solo en los pasajes tranquilos.
Una aclaración de frontera, para no confundir herramientas. El golden master compara la salida completa contra una foto; es ideal cuando la salida es rica (muchos casos, o una estructura grande como un JSON o un reporte) y no tienes una especificación de qué debería dar —solo sabes que "debe seguir dando lo mismo"—. Es primo del approval testing (donde apruebas una salida de referencia). No lo confundas con el property-based testing, que verifica propiedades generales ("el precio nunca es negativo") en vez de fijar salidas exactas; esa es otra técnica, del ecosistema de Testing, con otro propósito. Aquí, para migrar, queremos fijar la salida exacta del legacy, y para eso el golden master es la herramienta.
flowchart TD
S["muestreo con semilla fija<br/>(500 entradas reproducibles)"] --> L["correr el legacy<br/>(intacto)"]
L --> GM["golden_master<br/>(archivo versionado: 500 salidas)"]
C["reimplementacion candidata"] --> PR["parallel_run<br/>(mismas 500 entradas)"]
GM --> PR
PR --> D{"diferencias?"}
D -- "no" --> V["VERDE: comportamiento preservado"]
D -- "si" --> R["ROJO: 42 discrepancias<br/>todas en clientes gold"]
Errores comunes
Grabar el golden master con entradas no reproducibles. Qué pasa: se generan las entradas con random sin semilla fija (o con un orden que depende del ambiente), así que cada corrida produce entradas distintas. Por qué pasa: "aleatorio" suena a "más cobertura", y fijar la semilla parece un detalle. Cómo detectarlo: si corres la grabación dos veces y el golden master sale distinto sin que nadie tocara el legacy, no es reproducible. Cómo corregirlo: la semilla fija (random.Random(seed)) no es opcional, es la condición para que el golden master signifique algo. El master es una foto de una escena concreta; si la escena cambia cada vez, la foto no sirve para comparar. Reproducibilidad primero, siempre —es la misma disciplina de "datos y semilla fijos" que toda la guía usa para que los ejemplos sean verificables—.
Re-aprobar el golden master automáticamente cuando el diff sale rojo. Qué pasa: aparece una discrepancia, y para "poner el test en verde" se regenera el golden master sin mirar qué cambió. Por qué pasa: es la vía rápida para quitarse el rojo de encima, y algunas herramientas de approval testing lo facilitan demasiado (un botón de "aceptar todo"). Cómo detectarlo: si tu flujo regenera el master cada vez que hay diferencias, sin que un humano lea el diff, la red dejó de proteger nada —siempre estará verde porque siempre se re-aprueba—. Cómo corregirlo: el diff del parallel-run es la información más valiosa que tienes: es la lista literal de lo que tu cambio altera en producción. Léelo. Si los cambios son deseados (el negocio decidió recalcular), re-apruebas conscientemente. Si no —como las 42 discrepancias gold del ejemplo, que rompen el rebate del socio—, arreglas tu cambio, no el master. Re-aprobar a ciegas es firmar el acta de estado sin recorrer el departamento.
Confiar en el muestreo aleatorio para cubrir los bordes por sí solo. Qué pasa: se generan 500 entradas uniformes y se asume que "con tantas, seguro caen todos los casos raros". Por qué pasa: 500 suena a mucho, y la intuición dice que el azar cubre todo. Cómo detectarlo: revisa si tus entradas de muestreo incluyen garantizados los bordes críticos —cantidad exactamente 10 (el umbral de volumen), cupones al máximo, todas las categorías, clientes gold en cantidad suficiente—. Si dependes de que el azar los produzca, algunos casos raros pueden quedar sub-representados o ausentes. Cómo corregirlo: combina muestreo aleatorio con casos borde forzados —siembra a propósito ítems en cada umbral y cada combinación crítica, además de los aleatorios—. En el ejemplo tuvimos suerte de que el azar produjera suficientes clientes gold para acorralar la rareza; en un caso real no dejes esa cobertura al azar. La fábrica de discos no muestrea solo los pasajes fáciles: pone puntos de control justo donde el sonido es difícil.
Ejercicios
Ejercicio 1 — ¿Por qué solo 42, y por qué todas gold? En el parallel-run, de 500 entradas difirieron exactamente 42, y las 42 eran de clientes gold —pero había muchos más de 42 clientes gold en el lote—. Explica dos cosas: (a) por qué todas las discrepancias son de clientes gold y ninguna de no-gold; (b) por qué no todos los clientes gold difieren, solo 42.
Ver solución
(a) Por qué todas las discrepancias son gold. La única diferencia entre legacy_catalog_price y candidate_catalog_price es el orden en que se aplica la lealtad gold respecto al impuesto (después en el legacy, antes en la candidata). Esa diferencia solo puede producir un resultado distinto si la rama gold se ejecuta —es decir, si el ítem es cliente gold—. Para todo ítem no-gold, ambas funciones ejecutan exactamente los mismos pasos en el mismo orden, así que dan idéntico y no pueden aparecer en el diff. Por eso el 100% de las discrepancias cae en clientes gold: es el único segmento donde el código que cambió llega a ejecutarse.
(b) Por qué no todos los gold difieren. Aunque la rama gold se ejecute en todos los clientes gold, el resultado final solo difiere cuando el cambio de orden, combinado con el redondeo hacia abajo en cada paso, hace caer el último centavo en un lugar distinto. En muchos casos gold, aplicar el 3% antes o después del impuesto termina —tras los _floor_cents— en el mismo centavo, porque el redondeo absorbe la diferencia. Solo en 42 de ellos los números caen de forma que el centavo final cambia. Es decir: la rareza está presente en todos los cálculos gold, pero solo se manifiesta como diferencia observable en 42. Esto es justo lo que hace la regresión tan escurridiza a mano —probar un cliente gold cualquiera podría caer en uno de los que no difieren y darte verde en falso—, y por eso el volumen del golden master es lo que la atrapa con seguridad.
Ejercicio 2 — Diseña el muestreo. Vas a grabar un golden master del precio del catálogo y quieres asegurarte de cubrir los bordes en vez de dejarlos al azar. Describe cómo construirías las entradas de muestreo para garantizar que aparezcan: (a) el umbral de volumen (cantidad 9, 10, 11); (b) todas las categorías, incluida grocery con descuento 0; (c) clientes gold en proporción suficiente. Explica por qué "5000 entradas puramente aleatorias" no garantiza (a) tan bien como tu enfoque.
Ver solución
Un enfoque robusto combina casos borde forzados con relleno aleatorio reproducible:
- (a) Umbral de volumen: en vez de esperar que el azar produzca cantidades 9, 10 y 11, las siembras explícitamente. Generas, para varios precios y categorías, ítems con
quantityfijada en 9, 10 y 11 —los tres puntos alrededor del umbral—, para forzar que el golden master fije tanto el caso "sin descuento por volumen" (9) como el "con descuento" (10, 11) y el borde exacto. - (b) Todas las categorías: iteras sobre
list(CATEGORY_DISCOUNT)y generas al menos varios ítems de cada categoría, incluyendogrocery(descuento 0). Así garantizas que la rama de cada categoría —incluida la que "no descuenta"— quede fijada, en vez de confiar en que el azar elija grocery suficientes veces. - (c) Clientes gold: en lugar de un
choice([None, None, None, "gold"])que da ~25% gold por azar, generas un bloque explícito de ítems gold (por ejemplo, la mitad del lote gold y la mitad no-gold), o al menos fijas una proporción mínima garantizada. Así aseguras suficiente masa gold para que la rareza —que solo se manifiesta en algunos casos gold— tenga oportunidad de aparecer.
Además de esos casos borde forzados, agregas un relleno de N entradas aleatorias con semilla fija para barrer el resto del espacio (combinaciones que no anticipaste).
Por qué "5000 aleatorias" no garantiza (a) tan bien: el muestreo puramente aleatorio cubre el espacio en promedio, pero no garantiza ningún caso específico. Con quantity uniforme entre 1 y 20, la probabilidad de que caiga exactamente en 10 es baja, y podrías tener miles de entradas sin un solo quantity == 10 para cierta categoría con cierto cupón —el borde exacto donde vive el bug del umbral—. El azar da cobertura amplia pero con hoyos impredecibles justo en los puntos finos; sembrar los bordes a propósito los cierra. Volumen aleatorio + bordes forzados es más robusto que solo subir el volumen aleatorio.
Ejercicio 3 — El diff es la información. Corres un parallel-run de tu reimplementación del catálogo contra el golden master y salen 42 discrepancias, todas gold, con diferencias de un centavo en ambas direcciones. Un compañero dice: "son solo centavos y en promedio se cancelan, regeneremos el master y sigamos". Explica por qué esa reacción tira a la basura justo la información más valiosa, y qué deberías hacer antes de decidir cualquier cosa. Relaciónalo con lo que la lección 6 va a mostrar.
Ver solución
La reacción "regeneremos el master y sigamos" tira a la basura la información más valiosa porque el diff no es ruido: es un diagnóstico preciso de qué cambió tu reimplementación y para quién. Las 42 discrepancias te están diciendo tres cosas concretas: que tu cambio afecta solo a clientes gold (te dice el segmento), que la causa está en cómo se combina la lealtad con el impuesto y el redondeo (te dice el mecanismo), y que el efecto es de un centavo hacia arriba o hacia abajo (te dice la magnitud y que no es un sesgo uniforme). Regenerar el master sin leer eso borra el diagnóstico y "aprueba" a ciegas un cambio de comportamiento en producción.
Lo de "en promedio se cancelan" es un error doble. Primero, que se cancelen en agregado no significa que se cancelen por cliente: al cliente gold GM-0016 le cobras un centavo de más y al GM-0042 un centavo de menos —cada uno recibe un precio distinto del que el legacy le daba, aunque la suma cuadre—. Segundo, y más grave, hay consumidores que no leen el agregado sino el número individual de cada orden.
Lo que deberías hacer antes de decidir nada: leer el diff y entender por qué difiere. Al hacerlo descubrirías que las 42 discrepancias son la rareza gold (lealtad después del impuesto) que tu reimplementación "limpió" a antes del impuesto. Y ahí es donde entra la lección 6: ese comportamiento raro no es un bug libre de tocar —el rebate que Mercado le paga a un socio de lealtad se concilia contra esos números exactos, centavo por centavo—. "Regenerar el master y seguir" habría desplegado la regresión y roto la conciliación con el socio semanas después. El diff no era un estorbo que quitar; era la advertencia que evita una disputa de negocio.
Resumen y siguiente paso
En esta lección escalaste la caracterización de un puñado de casos a un espacio de entradas que ningún humano enumeraría a mano, con dos herramientas que van juntas: muestrear (generar muchas entradas reproducibles con semilla fija) y grabar un golden master (la foto de las salidas del legacy, la copia maestra aprobada). Viste, con la fábrica de discos, que el control de calidad no pregunta "¿esto es bueno?" sino "¿esto es idéntico al master?", y que muestrea en vez de escuchar cada segundo. Y lo mediste: un parallel-run de 500 entradas comparó una reimplementación candidata contra el golden master y encontró 42 discrepancias, todas de clientes gold, sin que nadie hubiera dirigido la búsqueda hacia ese segmento —la rareza quedó acorralada por el barrido, con diferencias de un centavo en ambas direcciones y solo en algunos casos gold, exactamente el tipo de regresión que seis casos a mano jamás atraparían—.
Antes de avanzar deberías poder: explicar por qué el golden master compara contra una foto en vez de verificar correctitud; grabar uno de forma reproducible (semilla fija) y usarlo en un parallel-run; leer el diff como diagnóstico en vez de re-aprobar a ciegas; y sembrar los bordes en el muestreo en lugar de confiar solo en el azar.
La lección 6 toma la discrepancia que este método encontró —la rareza gold— y hace la pregunta más incómoda del módulo: ¿qué haces cuando el comportamiento raro que fijaste resulta ser algo de lo que alguien depende? Vas a ver la ley de Hyrum en acción —con suficientes consumidores, cada comportamiento observable se vuelve un contrato— y vas a medir cómo "arreglar" la rareza gold cambia el rebate del socio de lealtad por un centavo y rompe la conciliación. El bug que era, en realidad, una feature.
Recursos
- Martin Fowler, "Patterns of Legacy Displacement" — martinfowler.com/articles/patterns-legacy-displacement. Correr la implementación vieja y la nueva sobre las mismas entradas y comparar sus salidas; el parallel run como patrón nombrado es de Sam Newman (Monolith to Microservices). El mecanismo exacto de esta lección. En inglés.
- Emily Bache, "Approval Testing" y la herramienta ApprovalTests — approvaltests.com. El golden master llevado a práctica moderna: aprobar una salida de referencia y compararla en cada corrida, con soporte para revisar el diff antes de re-aprobar. En inglés.
- Michael Feathers, Working Effectively with Legacy Code (Prentice Hall, 2004), cap. 13 — la caracterización a escala y la idea de cubrir el comportamiento con muchos casos, base conceptual del golden master. En inglés.
- Llewellyn Falco, "Golden Master / Testing legacy code" — charlas y ejemplos sobre cómo grabar un golden master para refactorizar código legacy con seguridad, muy alineado con el flujo de esta lección. En inglés.