Módulo 2: Entender y fijar el legacy

El characterization test: fija lo que hace HOY

Descripción

Ya sabes qué hace un characterization test (fija el comportamiento actual) y por qué lo necesitas (sin la red, el cambio a ciegas cuesta carísimo). Esta lección enseña cómo se escribe, y lo hace deteniéndose en la trampa que atrapa a casi todos la primera vez. Cuando te sientas a escribir un test, tu instinto —bien entrenado— es este: primero pienso cuál es la respuesta correcta, la escribo como valor esperado, y luego verifico que el código la produzca. Ese instinto es exactamente el correcto para el código que tú diseñas, y exactamente el equivocado para el código legacy que estás caracterizando. Con el legacy no sabes cuál es la respuesta "correcta" —para eso tendrías que entender años de reglas acumuladas— y, peor, no te importa: no estás verificando que el precio sea correcto, estás fijando lo que el legacy devuelve hoy. La respuesta esperada no la calculas: la capturas corriendo el código y leyendo lo que sale.

Esa inversión —capturar en vez de adivinar— es todo el arte del characterization test, y suena más fácil de lo que es, porque el instinto de "arreglar de paso" lucha contra ti en cada línea. Si al leer el código ves un redondeo que te parece mal y escribes el valor esperado que debería dar, tu test va a salir rojo contra el legacy —y el peligro es que entonces "arregles" el legacy para que pase tu test, cambiando el comportamiento de producción sin haberlo decidido nadie—. El characterization test bien hecho hace lo contrario: se rinde ante lo que el legacy hace, lo transcribe con fidelidad total, rarezas incluidas, y solo después —como decisión aparte y consciente— alguien podrá preguntarse si ese comportamiento es el deseado.

Conexión con el módulo. La lección 2 justificó la red; esta te enseña a tejerla. Es la técnica central del módulo: las lecciones que siguen la extienden (la 4 la hace posible cuando una dependencia lo impide; la 5 la escala a cientos de casos; la 6 enfrenta qué hacer cuando lo que fijaste es una rareza de la que alguien depende). Aquí instalamos el gesto fundamental: capturar la salida actual, no adivinar la ideal. La frontera con el ecosistema de Testing se mantiene firme: no vamos a discutir cómo se estructura una suite ni qué es un assert; vamos a usar la aserción como instrumento para fijar comportamiento antes de migrarlo.

Una analogía: el taquígrafo del juzgado

En un juzgado hay una persona cuyo trabajo es escribir, palabra por palabra, todo lo que se dice: el taquígrafo. Y su regla de oro es una que a un editor le daría escalofríos: transcribe exactamente lo que se dijo, incluidos los errores. Si un testigo dice "yo lo vide" en vez de "yo lo vi", el taquígrafo escribe "vide". Si alguien se equivoca en una fecha, la escribe equivocada. Si un abogado tartamudea, quedan los tartamudeos. ¿Por qué no lo "corrige de paso", si sería más prolijo? Porque su trabajo no es producir un texto correcto; es producir un registro fiel de lo que realmente ocurrió. Ese registro tiene valor legal precisamente por su fidelidad: si el taquígrafo "mejorara" lo dicho, el acta dejaría de ser prueba de nada. La corrección es tarea de otro momento y de otra persona —el juez decidirá qué peso dar a ese "vide"—; la del taquígrafo es capturar, no juzgar.

El characterization test es el taquígrafo del legacy. Corres la función, ves que para cierto ítem devuelve 106.88 —un número que sale de aplicar la lealtad después del impuesto, algo que a ti te parece un error— y lo transcribes tal cual: 106.88. No escribes 106.89, que es lo que "debería" dar según tu matemática limpia. Escribes lo que el legacy realmente dijo. Como el taquígrafo, tu trabajo aquí no es producir el precio correcto; es producir un registro fiel de lo que el legacy hace hoy. Si "corriges de paso", tu registro deja de servir como red —ya no fija el comportamiento real, fija tu opinión sobre él—. Y la pregunta de si 106.88 está bien o mal es de otro momento y de otra persona: el negocio decidirá (lección 6). El characterization test solo taquigrafía.

Guarda esta imagen, porque es el gesto que hay que aprender: ante el legacy, transcribe, no edites.

Ejemplo trabajado: no adivines, captura

Vamos a construir un characterization test de tres pasos sobre el precio del catálogo, y el ejemplo va a hacer visible la trampa. Primero comparamos, para tres ítems, el precio que tú "adivinarías" con matemática limpia contra el que el legacy realmente devuelve. Vas a ver que no coinciden. Luego capturamos la salida real y la fijamos. Y al final miramos qué fijó el test, a propósito, en el caso raro.

import math

TAX_RATE = 0.16
CATEGORY_DISCOUNT = {"electronics": 0.05, "books": 0.10, "toys": 0.08, "grocery": 0.00}
GOLD_LOYALTY_DISCOUNT = 0.03


def _floor_cents(a):
    return math.floor(a * 100) / 100


def legacy_catalog_price(item):
    price = _floor_cents(item["unit_price"] * item["quantity"])
    price = _floor_cents(price * (1 - CATEGORY_DISCOUNT.get(item["category"], 0.0)))
    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 guessed_price(item):
    # Lo que "deberia" costar segun matematica limpia: redondeo unico, gold pre-impuesto.
    price = item["unit_price"] * item["quantity"]
    price *= (1 - CATEGORY_DISCOUNT.get(item["category"], 0.0))
    price *= (1 - item.get("coupon_percent", 0.0))
    if item.get("loyalty") == "gold":
        price *= (1 - GOLD_LOYALTY_DISCOUNT)
    price *= (1 + TAX_RATE)
    return round(price, 2)


ITEMS = [
    {"sku": "A-1", "unit_price": 100.00, "quantity": 1, "category": "electronics"},
    {"sku": "D-4", "unit_price": 100.00, "quantity": 1, "category": "electronics",
     "loyalty": "gold"},
    {"sku": "E-5", "unit_price": 49.00,  "quantity": 2, "category": "books",
     "coupon_percent": 0.15},
]

print("Paso 1 - NO adivines la respuesta 'correcta'. Compara adivinada vs real:")
print(f"{'sku':<6}{'adivinada':>12}{'real (legacy)':>16}{'iguales?':>11}")
for it in ITEMS:
    g, r = guessed_price(it), legacy_catalog_price(it)
    print(f"{it['sku']:<6}{g:>12}{r:>16}{str(g==r):>11}")
print("Un test escrito con la columna 'adivinada' saldria ROJO contra el legacy")
print("de HOY. Y 'arreglarlo' para que pase cambiaria el precio en produccion.\n")

print("Paso 2 - CAPTURA la salida real y fijala como valor esperado:")
# La tecnica: corres el legacy, LEES lo que da, y pegas ese numero como esperado.
captured = {it["sku"]: legacy_catalog_price(it) for it in ITEMS}
for sku, value in captured.items():
    print(f"  assert legacy_catalog_price(item_{sku}) == {value}")
print()

print("Paso 3 - Corre el characterization_test con los valores capturados:")
fails = 0
for it in ITEMS:
    expected = captured[it["sku"]]
    actual = legacy_catalog_price(it)
    ok = actual == expected
    fails += 0 if ok else 1
    print(f"  {'PASS' if ok else 'FAIL'}  {it['sku']}  "
          f"esperado={expected:<8} actual={actual}")
print(f"  -> {'VERDE' if fails==0 else 'ROJO'}: el test NO dice que el precio este "
      f"'bien'; dice que sigue siendo el de HOY.\n")

print("Lo que el test fija a proposito (la rareza D-4):")
d4 = ITEMS[1]
print(f"  matematica 'limpia' diria: {guessed_price(d4)} (gold antes del impuesto)")
print(f"  el legacy cobra:           {legacy_catalog_price(d4)} (gold DESPUES del impuesto)")
print("  El characterization_test fija 106.88, la rareza incluida. Si manana")
print("  alguien la 'arregla', el test se pone ROJO y lo obliga a decidirlo a proposito.")

Qué esperar. Al correr el archivo, la salida es exactamente esta:

Paso 1 - NO adivines la respuesta 'correcta'. Compara adivinada vs real:
sku      adivinada   real (legacy)   iguales?
A-1          110.2          110.19      False
D-4         106.89          106.88      False
E-5          86.97           86.96      False
Un test escrito con la columna 'adivinada' saldria ROJO contra el legacy
de HOY. Y 'arreglarlo' para que pase cambiaria el precio en produccion.

Paso 2 - CAPTURA la salida real y fijala como valor esperado:
  assert legacy_catalog_price(item_A-1) == 110.19
  assert legacy_catalog_price(item_D-4) == 106.88
  assert legacy_catalog_price(item_E-5) == 86.96

Paso 3 - Corre el characterization_test con los valores capturados:
  PASS  A-1  esperado=110.19   actual=110.19
  PASS  D-4  esperado=106.88   actual=106.88
  PASS  E-5  esperado=86.96    actual=86.96
  -> VERDE: el test NO dice que el precio este 'bien'; dice que sigue siendo el de HOY.

Lo que el test fija a proposito (la rareza D-4):
  matematica 'limpia' diria: 106.89 (gold antes del impuesto)
  el legacy cobra:           106.88 (gold DESPUES del impuesto)
  El characterization_test fija 106.88, la rareza incluida. Si manana
  alguien la 'arregla', el test se pone ROJO y lo obliga a decidirlo a proposito.

Lee el paso 1 con atención, porque ahí está la trampa hecha visible. Para los tres ítems, el precio que la matemática "limpia" adivina y el que el legacy realmente devuelve no coinciden —110.2 vs 110.19, 106.89 vs 106.88, 86.97 vs 86.96—. En los tres, la diferencia es de un centavo, y en los tres el legacy cobra menos, porque redondea hacia abajo en cada paso mientras la versión limpia redondea una sola vez al final. Ahora imagina que caíste en la trampa: escribiste el test con la columna "adivinada" —los valores que te parecían correctos—. Ese test saldría rojo contra el legacy de hoy. Y aquí viene lo peligroso: la reacción natural ante un test rojo es "arreglar el código para que pase". Si lo hicieras, cambiarías el redondeo del legacy para que devuelva 110.2 en vez de 110.19... alterando el precio de producción para que coincida con tu opinión de cómo debería calcularse. Habrías usado un test como excusa para cambiar comportamiento sin que nadie lo decidiera. Ese es el desastre que la trampa produce.

El paso 2 es la salida: en vez de adivinar, capturas. Corres el legacy, lees lo que devuelve (110.19, 106.88, 86.96) y pegas esos números como valores esperados. Fíjate en las tres líneas de assert que imprime: son el characterization test literal. No dicen "el precio de A-1 debe ser 110.19 porque así lo calculé"; dicen "el precio de A-1 es 110.19 hoy, y quiero que siga siéndolo". El valor esperado salió del propio legacy, no de tu cabeza.

El paso 3 corre esos asserts y sale verde, obviamente —capturamos los valores del mismo legacy—. Pero el verde aquí significa algo preciso que conviene repetir: no dice que 110.19 sea el precio correcto; dice que el legacy sigue devolviendo lo que devolvía cuando tomamos la foto. Es la garantía de no-cambio, no de correctitud.

Y el bloque final es la lección más delicada del módulo en germen. Mira D-4: la matemática limpia dice 106.89, el legacy cobra 106.88, y el characterization test fija 106.88 —la rareza incluida—. El test protege el "error" a propósito. ¿Por qué protegerías un error? Porque tu trabajo hoy no es corregirlo, es fijarlo, y porque —como verás en la lección 6— resulta que alguien depende de ese centavo. Al fijar 106.88, el test hace algo valiosísimo: si mañana alguien "limpia" el cálculo para que dé 106.89, el test se pone rojo y lo obliga a darse cuenta de que está cambiando la rareza. No se lo prohíbe; lo hace visible y consciente. Ese es el punto entero: el characterization test no impide corregir bugs; impide corregirlos por accidente.

Profundización: el characterization test como "test que aprende"

Hay una forma práctica de escribir estos tests que Feathers describe y que vale la pena tener a mano, porque resuelve el problema de "no sé qué valor poner". El truco es dejar que el test te diga el valor. Escribes la aserción con un valor que sabes falso —por ejemplo, cero— corres el test, y lees en el mensaje de fallo el valor real que el legacy produjo. Luego copias ese valor real a la aserción. El test acaba de "aprender" el comportamiento actual, y a partir de ahí queda en verde y protege ese comportamiento. En pseudocódigo del flujo:

1. assert legacy_catalog_price(item_A1) == 0        # valor deliberadamente falso
2. corres -> FALLA: "esperado 0, obtenido 110.19"   # el legacy te dicta el valor
3. assert legacy_catalog_price(item_A1) == 110.19   # copias el valor REAL
4. corres -> VERDE                                  # el test aprendio el comportamiento

Fíjate en la inversión respecto a un test normal. En un test que escribes para código tuyo, tú conoces la respuesta esperada de antemano (la diseñaste) y el código tiene que alcanzarla. En un characterization test, el código conoce la respuesta y tú la aprendes de él. Por eso a veces se les llama "learning tests": no imponen una expectativa, la extraen. Y por eso no hay riesgo de "adivinar mal": el valor nunca sale de tu cabeza, siempre sale de correr el legacy.

Esto conecta con un cuidado importante. El valor que capturas es tan bueno como las entradas que elegiste. Si solo caracterizas el camino feliz —un ítem simple, sin cupón, sin gold, cantidad 1— tu red va a estar ciega justo en los casos raros, que son los que más se rompen (recuerda que D-4, el gold, fue el único que la reimplementación "limpia" rompió). Un buen characterization test cubre el camino feliz y los casos borde: cantidades que cruzan el umbral de volumen (9, 10, 11), cupones al límite, clientes gold, categorías con descuento 0, precios que redondean feo. La lección 5 lleva esto a su conclusión: cuando los casos borde son demasiados para elegirlos a mano, se muestrea el espacio de entradas y se graba un golden master. Pero el principio es el mismo desde ahora: transcribe muchos casos, no solo el bonito.

Una última precisión sobre qué se fija. Aquí caracterizamos el valor de retorno de una función pura, que es el caso más limpio. Pero el comportamiento observable de un legacy incluye a veces más que el retorno: qué escribe en la base de datos, qué correo dispara, qué registro deja en el log. Caracterizar esos efectos es más laborioso (hay que capturarlos), pero la idea no cambia: fijas lo que el sistema hace de forma observable, no lo que crees que debería hacer. En el catálogo de Mercado el precio es un retorno puro, así que nos concentramos en él; ten presente que la técnica se extiende a efectos cuando haga falta.

Errores comunes

Adivinar el valor esperado en vez de capturarlo. Qué pasa: al escribir el test, calculas mentalmente (o con una fórmula "limpia") cuál debería ser el resultado y lo pones como esperado, en vez de correr el legacy y copiar lo que devuelve. Por qué pasa: es el instinto correcto para el código que diseñas, y cuesta apagarlo. Cómo detectarlo: si tu characterization test sale rojo la primera vez que lo corres contra el legacy sin haber cambiado nada, casi seguro adivinaste el valor en vez de capturarlo —el legacy no puede "fallar" contra su propia salida—. Cómo corregirlo: usa el flujo del "test que aprende": pon un valor falso, corre, lee el valor real del mensaje de fallo, cópialo. El valor esperado tiene que salir del legacy, nunca de tu cabeza. Como el taquígrafo: transcribes lo que se dijo, no lo que debió decirse.

"Arreglar" el legacy para que pase tu test. Qué pasa: escribiste el test con un valor adivinado, sale rojo, y en vez de corregir el test corriges el código para que produzca tu valor. Por qué pasa: "test rojo → arreglar el código" es un reflejo tan fuerte que se dispara aunque el "arreglo" cambie comportamiento de producción. Cómo detectarlo: si tu primer commit sobre el legacy cambia lo que devuelve para casos existentes, y lo justificas con "es que el test no pasaba", invertiste la relación —el test debía describir el legacy, no reformarlo—. Cómo corregirlo: en la fase de caracterización, el legacy es la verdad y el test se adapta a él, nunca al revés. Si de verdad hay un bug que quieres corregir, esa es una decisión aparte y posterior (lección 6), que se hace después de tener la red y de forma consciente, no colándola bajo la excusa de "hacer pasar el test".

Caracterizar solo el camino feliz. Qué pasa: fijas un par de casos simples y bonitos —un producto normal, sin descuentos raros— y das la red por hecha. Por qué pasa: los casos simples son los primeros que se te ocurren y los más rápidos de escribir, y dan una falsa sensación de cobertura. Cómo detectarlo: si tu suite no incluye clientes gold, ni cantidades en el umbral de volumen, ni cupones, ni categorías con descuento 0, estás ciego justo donde el legacy es más raro. Cómo corregirlo: el legacy esconde su comportamiento peligroso en los bordes, no en el centro —recuerda que la única regresión que la reimplementación "limpia" produjo fue en el cliente gold, un caso que un camino feliz sin gold jamás habría detectado—. Cubre deliberadamente los bordes: valores en los umbrales, combinaciones de descuentos, segmentos especiales. Y cuando los bordes sean demasiados para enumerarlos, muestrea (lección 5). Una red con hoyos en los bordes es casi tan peligrosa como no tener red, porque te da confianza donde no deberías tenerla.

Ejercicios

Ejercicio 1 — El valor que capturas. Tienes esta función legacy de Mercado y quieres caracterizarla para el ítem {"unit_price": 10.00, "quantity": 3, "category": "books"}. La categoría books da 10% de descuento; el impuesto es 16%; el redondeo es hacia abajo al centavo en cada paso. Sin correr nada, describe el procedimiento correcto para obtener el valor esperado del characterization test (no te pedimos el número exacto, sino el método). Luego explica por qué está mal el enfoque de "calculo 10×3×0.9×1.16 y pongo ese número".

Ver solución

El procedimiento correcto es capturar, no calcular: corres la función legacy real con ese ítem exacto, lees el valor que devuelve, y ese valor —el que salió del código— es el que pones como esperado en el test. Si quieres, usas el flujo del "test que aprende": escribes assert precio == 0, corres, y el mensaje de fallo te dice el valor real, que copias a la aserción. El punto es que el número esperado sale de ejecutar el legacy, no de una fórmula que tú escribiste.

El enfoque de "calculo 10×3×0.9×1.16 = 31.32 y pongo ese número" está mal por dos razones. Primero, reproduce tu modelo mental del cálculo, no el del legacy. Tu fórmula redondea una sola vez al final (implícito en dar 31.32); el legacy redondea hacia abajo en cada paso, así que su resultado real puede diferir por centavos. Si pones 31.32 y el legacy devuelve 31.31, tu test sale rojo contra un legacy que no cambió —falsa alarma— y te empujaría a "arreglar" el legacy para que dé tu número. Segundo, y más de fondo: el characterization test no verifica que tu fórmula sea correcta; fija lo que el legacy hace. Aunque tu fórmula fuera "más correcta" contablemente, el test debe capturar el comportamiento real para poder detectar cambios respecto a él. La corrección es una pregunta posterior y separada.

Ejercicio 2 — Verde no es correcto. Un characterization test del catálogo fija que cierto ítem gold cuesta 106.88 y sale verde. Un desarrollador argumenta: "el test pasa, entonces aplicar la lealtad después del impuesto es la forma correcta de calcular el precio". Explica el error de razonamiento, apoyándote en la analogía del taquígrafo, y di qué tendría que ocurrir para poder afirmar que 106.88 es el precio correcto.

Ver solución

El error es confundir "el test transcribe fielmente el comportamiento" con "el comportamiento es el correcto". El characterization test que fija 106.88 hace exactamente lo que el taquígrafo del juzgado: registró que el legacy dice 106.88, con la misma neutralidad con que el taquígrafo escribe "vide" cuando el testigo dice "vide". El verde significa "el legacy sigue diciendo lo que dijo cuando lo transcribimos" —no "lo que dice es verdad—". Un taquígrafo que transcribe una mentira sin errores no la convierte en verdad; solo deja constancia fiel de que se dijo. Igual, un characterization test verde sobre un bug no convierte el bug en correcto; solo certifica que el bug sigue ahí, idéntico.

Para afirmar que 106.88 es el precio correcto haría falta algo que el test no contiene: la especificación de negocio de cómo debe calcularse el precio de un cliente gold —¿la lealtad va antes o después del impuesto?—, y la confirmación de que 106.88 corresponde a esa regla deseada. Esa es una decisión de producto/negocio (y, como muestra la lección 6, puede tener consecuencias: un socio ya depende del 106.88). El characterization test es neutral respecto a esa pregunta por diseño: su trabajo es detectar cambios, no arbitrar correctitud.

Ejercicio 3 — Diseña la batería de entradas. Vas a caracterizar el precio del catálogo y solo tienes tiempo para elegir 6 ítems a mano. Basándote en la regla "cubre los bordes, no solo el camino feliz", propón 6 ítems que juntos ejerciten el comportamiento peligroso de la función (categoría, volumen con umbral en 10, cupón, lealtad gold, redondeo). Para cada uno, di en una frase qué borde o combinación estás cubriendo, y explica por qué una batería de "6 productos normales sin descuentos" sería una red con hoyos.

Ver solución

Una batería razonable (los valores exactos pueden variar; lo que importa es qué borde cubre cada uno):

  1. {unit_price: 100, quantity: 1, category: "electronics"} — camino base simple, un solo descuento (categoría), sin combinaciones. La referencia.
  2. {unit_price: 100, quantity: 9, category: "electronics"} — cantidad justo debajo del umbral de volumen (10): verifica que el descuento por volumen no se aplique.
  3. {unit_price: 100, quantity: 10, category: "electronics"} — cantidad justo en el umbral: verifica que el descuento por volumen se aplique. Los casos 2 y 3 juntos fijan el borde del umbral, un lugar clásico de bugs.
  4. {unit_price: 100, quantity: 1, category: "electronics", loyalty: "gold"} — cliente gold: ejercita la rareza (lealtad después del impuesto), el caso que rompió la reimplementación "limpia".
  5. {unit_price: 49, quantity: 2, category: "books", coupon_percent: 0.15} — combina categoría y cupón: verifica el orden de aplicación de dos descuentos y el redondeo acumulado.
  6. {unit_price: 7.50, quantity: 12, category: "toys", coupon_percent: 0.10, loyalty: "gold"} — el "todo junto": volumen + cupón + gold + un precio que redondea feo. El caso más rico, donde el redondeo por pasos se nota más.

Una batería de "6 productos normales sin descuentos" sería una red con hoyos porque solo ejercitaría el centro tranquilo de la función —subtotal, categoría, impuesto— y dejaría sin fijar justo las ramas donde el legacy es raro y frágil: el umbral de volumen, la combinación de descuentos, la rareza gold, el redondeo en casos difíciles. Como esas ramas no estarían en la foto, un cambio que las rompiera pasaría el characterization test en verde —la red daría luz verde a una regresión—. Y esos bordes no son casos exóticos de museo: el cliente gold y el cupón son clientes reales de Mercado todos los días. La red tiene que cubrir donde el legacy hace cosas raras, que es exactamente donde no está el camino feliz.

Resumen y siguiente paso

En esta lección aprendiste a escribir la red, y el gesto central que la hace funcionar: capturar la salida actual del legacy, no adivinar la ideal. Viste, con el taquígrafo, que tu trabajo al caracterizar es transcribir con fidelidad total lo que el legacy hace —rarezas y "errores" incluidos—, no producir el resultado correcto; la corrección es una pregunta posterior y de otra persona. Y lo mediste: el paso 1 mostró que la matemática "limpia" adivina valores que no coinciden con los reales (110.2 vs 110.19), de modo que un test hecho con valores adivinados saldría rojo contra un legacy sano y te empujaría a alterar producción; el paso 2 capturó los valores reales; y el paso 3 los fijó, incluida la rareza gold (106.88, no 106.89), para que cualquier "arreglo" futuro tenga que ser consciente.

Antes de avanzar deberías poder: explicar por qué el valor esperado se captura y no se calcula; usar el flujo del "test que aprende" para extraer el valor del propio legacy; distinguir "verde" (no cambió) de "correcto" (cumple la especificación); y diseñar una batería de entradas que cubra los bordes y no solo el camino feliz.

La lección 4 ataca el obstáculo que en la práctica frena la caracterización más seguido: ¿qué haces cuando no puedes fijar el comportamiento porque una dependencia lo hace impredecible? El precio del catálogo consulta el reloj real para saber si un cupón venció, así que su salida cambia según el día en que corras el test —imposible de fijar de forma determinista—. Vas a aprender a encontrar el seam: el punto donde desviar esa dependencia (el reloj) sin reescribir la función, para que el test pueda gobernarla.

Recursos

  • Michael Feathers, Working Effectively with Legacy Code (Prentice Hall, 2004), cap. 13 "I Need to Make a Change, but I Don't Know What Tests to Write" — el capítulo que define el characterization test y la técnica del "test que aprende" (poner un valor falso y dejar que el código dicte el real). La fuente directa de esta lección. En inglés.
  • "Characterization test" — término de Michael Feathers (Working Effectively with Legacy Code); resumen en Wikipedia: en.wikipedia.org/wiki/Characterization_test. La distinción, en una página, entre fijar comportamiento y verificar correctitud —el malentendido que esta lección combate—. En inglés.
  • Emily Bache, "The Gilded Rose Kata" — github.com/emilybache/GildedRose-Refactoring-Kata. El ejercicio clásico para practicar characterization tests sobre una función legacy enredada antes de refactorizarla; la contraparte práctica del catálogo de Mercado. En inglés.
  • Nicolas Carlo, "How to add a test to legacy code (characterization tests)" — understandlegacycode.com. Guía paso a paso, moderna y con ejemplos, del mismo flujo de captura que usamos aquí. En inglés.