Mantén tus tests DAMP, no DRY
Todo lo que sabes sobre DRY está mal — en los tests. En el código de la aplicación, la duplicación es deuda: dos copias de una misma regla van a divergir, y una de ellas va a estar mal. Pero los tests tienen otro trabajo. Un test es una especificación que tiene que explicarse sola a las 2 a.m. cuando falla. Los helpers sobre-compartidos te obligan a perseguir la lógica por cinco archivos para entender una sola falla. El equipo de testing de Google le puso nombre a la alternativa: mantén los tests DAMP — Descriptive And Meaningful Phrases (frases descriptivas y con significado).
Por qué DRY sale mal en código de tests
El código de producción se lee por máquinas y se mantiene con abstracción. El código de tests lo lee un humano bajo presión, normalmente justo después de un build en rojo. Cada capa entre el nombre del test y la aserción — un bloque setUp tres pantallas más arriba, una fábrica makeUser() en otro archivo, un assertValidResponse() que verifica nueve cosas — es una página que el lector tiene que sostener en la cabeza.
El libro Software Engineering at Google lo dice claro: "un poco de duplicación está bien en los tests siempre que esa duplicación haga el test más simple y más claro". Los tests necesitan completitud (todo lo necesario para entender el test está en su cuerpo) y concisión (nada irrelevante lo está). El DRY estricto no optimiza ninguna de las dos.
Hay un segundo modo de falla: los helpers compartidos acumulan condicionales. Kent C. Dodds llama a la solución AHA — Avoid Hasty Abstraction (evita la abstracción apresurada). Una utilidad de test que empezó con cinco líneas crece un objeto de opciones, luego un flag por cada llamador, hasta que leer el helper es más difícil que leer diez tests duplicados.
Reglas prácticas
| Situación | Aplícale DRY | Déjalo DAMP |
|---|---|---|
| Boilerplate de construcción de objetos | Helper con defaults sobrescribibles | — |
| Los valores sobre los que el test hace aserciones | — | Inline, visibles en el cuerpo del test |
| Un paso nombrado en el escenario ("iniciar sesión como admin") | — | Escríbelo o nómbralo descriptivamente |
| La plomería detrás de ese paso (llamadas HTTP, fixtures) | Extráela | — |
| Aserción de un solo hecho conceptual | Un helper de validación enfocado está bien | — |
| Helper genérico de "validar todo" | Nunca | Divídelo en asserts por hecho |
El patrón detrás de la tabla es la división de Vladimir Khorikov: aplica DAMP a los qué (el escenario que un lector debe seguir) y DRY a los cómo (la mecánica por debajo). Los dos principios en realidad no chocan — DRY trata de no duplicar conocimiento del dominio, y dos tests que casualmente comparten cuatro líneas de setup no duplican conocimiento: son coincidentemente similares.
Cómo se ve un test DAMP
def test_expired_coupon_is_rejected():
coupon = make_coupon(expires="2025-01-01") # helper hides plumbing...
cart = Cart(items=[book(price=30)])
result = cart.apply(coupon, today="2026-07-22") # ...values stay visible
assert result.rejected
assert result.reason == "coupon expired"
El helper construye el objeto; los valores con significado — la fecha de expiración, la fecha de hoy, la razón del rechazo — viven en el test. Cuando esto falla, el diagnóstico está en una sola pantalla. Sin safari de helpers.
Trucos avanzados
- Helpers con defaults, tests con overrides.
make_user(status="suspended")— cada valor que le importa al test aparece en el test; cada valor que no, es un default que nunca menciona. - Elimina la constante compartida.
TEST_AMOUNT = 42no le dice nada al lector. Si el valor importa, ponlo inline donde se asevera; si no importa, deja que un default lo esconda por completo. - Un helper de aserción por hecho.
assert_emails_sent(n=1)es compatible con DAMP;assert_response_ok(resp, check_headers=True, check_body=False)es una granja de condicionales. - Esto importa el doble con agentes de IA. Los agentes de código leen tus tests como la especificación de facto del comportamiento esperado. Un test DAMP auto-contenido le da al agente el contrato completo en un solo lugar; un test DRY lo obliga a expandir cinco helpers en su contexto — y le da más oportunidades de adivinar mal.
- Refactoriza los tests hacia la legibilidad, no hacia el tamaño. Un test de 12 líneas que entiendes en 10 segundos le gana a uno de 4 líneas que exige un tour por el repo.
Recursos
- Testing on the Toilet: Tests Too DRY? Make Them DAMP! — Google Testing Blog
- Software Engineering at Google, cap. 12: Unit Testing (DAMP, Not DRY)
- AHA Testing — Kent C. Dodds
- DRY vs DAMP in Unit Tests — Vladimir Khorikov
¿Construyendo una funcionalidad con IA? Yeda AI diseña, audita y entrega sistemas LLM de producción.