Yeda AI Tips · #096

Español

Keep Tests DAMP, Not DRY

Everything you know about DRY is wrong — in tests. In app code, duplication is debt: two copies of one rule will drift, and one of them will be wrong. But tests have a different job. A test is a spec that has to explain itself at 2 a.m. when it fails. Over-shared helpers force you to chase logic through five files to understand one failure. Google's testing team gave the alternative a name: keep tests DAMPDescriptive And Meaningful Phrases.

Why DRY backfires in test code

Production code is read by machines and maintained through abstraction. Test code is read by a human under pressure, usually right after a red build. Every layer between the test name and the assertion — a setUp block three screens up, a makeUser() factory in another file, an assertValidResponse() that checks nine things — is a page the reader has to hold in their head.

The Software Engineering at Google book puts it plainly: "a little bit of duplication is OK in tests so long as that duplication makes the test simpler and clearer." Tests need completeness (everything required to understand the test is in its body) and conciseness (nothing irrelevant is). Strict DRY optimizes for neither.

There's a second failure mode: shared helpers accumulate conditionals. Kent C. Dodds calls the fix AHAAvoid Hasty Abstraction. A test utility that started as five lines grows an options object, then a flag per caller, until reading the helper is harder than reading ten duplicated tests would have been.

Rules of thumb

SituationDRY itKeep it DAMP
Object construction boilerplateHelper with overridable defaults
The values the test asserts onInline, visible in the test body
A step named in the scenario ("log in as admin")Spell it out or name it descriptively
The plumbing behind that step (HTTP calls, fixtures)Extract it
Assertion of one conceptual factFocused validation helper is fine
General-purpose "validate everything" helperNeverSplit into per-fact asserts

The pattern behind the table is Vladimir Khorikov's split: apply DAMP to the what-to's (the scenario a reader must follow) and DRY to the how-to's (the mechanics underneath). The two principles don't actually conflict — DRY is about not duplicating domain knowledge, and two tests that happen to share four setup lines aren't duplicating knowledge, they're coincidentally similar.

What a DAMP test looks like

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"

The helper builds the object; the meaningful values — the expiry date, today's date, the rejection reason — live in the test. When this fails, the diagnosis is on one screen. No helper safari.

Power-user moves

Resources

Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.

Talk to us · Read the blog