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 DAMP — Descriptive 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 AHA — Avoid 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
| Situation | DRY it | Keep it DAMP |
|---|---|---|
| Object construction boilerplate | Helper with overridable defaults | — |
| The values the test asserts on | — | Inline, 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 fact | Focused validation helper is fine | — |
| General-purpose "validate everything" helper | Never | Split 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
- Helpers with defaults, tests with overrides.
make_user(status="suspended")— every value a test cares about appears in the test; every value it doesn't is a default it never mentions. - Delete the shared constant.
TEST_AMOUNT = 42tells a reader nothing. If the value matters, inline it where it's asserted; if it doesn't, let a default hide it entirely. - One assert helper per fact.
assert_emails_sent(n=1)is DAMP-friendly;assert_response_ok(resp, check_headers=True, check_body=False)is a conditional farm. - This matters double with AI agents. Coding agents read your tests as the de-facto spec of intended behavior. A self-contained DAMP test gives the agent the full contract in one place; a DRY test forces it to expand five helpers into its context — and gives it more chances to guess wrong.
- Refactor tests toward readability, not size. A 12-line test you understand in 10 seconds beats a 4-line test that needs a repo tour.
Resources
- Testing on the Toilet: Tests Too DRY? Make Them DAMP! — Google Testing Blog
- Software Engineering at Google, Ch. 12: Unit Testing (DAMP, Not DRY)
- AHA Testing — Kent C. Dodds
- DRY vs DAMP in Unit Tests — Vladimir Khorikov
Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.