WET and DAMP In Testing
No, this isn't an article about the relative humidity of your house, or why your windows steam up in the winter; rather it's a brief guide/part rant about automated tests and how the over-application of DRY actually harms the readability and masks the intent of the test itself.
WET: Well Explained Tests
Sometimes you find testers who love shaving off lines of code in their tests like they’re being charged per character (remember when Twitter thought about doing this?). Somewhere along the way they convinced themselves that the perfect test is a three-line Zen koan that reveals nothing, proves nothing, and leaves future colleagues who come after them wondering whether the author was drunk or incompetent, or perhaps both.
Thinking "WET" is a way to address this. They say what they prove. They read like intent. They prioritise clarity over cleverness. They do not hide behaviour behind endless indirection.
DAMP: Descriptive and Meaningful Phrases
If WET is about tests that scream intent, then DAMP is the bit that keeps them from mumbling. Descriptive and Meaningful Phrases means naming things like you actually want another human to understand them.
Not cryptic abbreviations, not “helper2”, not “doesThing()”. Clear names carry the story of the test: what’s being set up, what behaviour is exercised, and what outcome matters. In the context of WET, DAMP is the glue that keeps everything readable.
So that's the premise anyway. What does it look like?
1. Bad: Over-Abstracted “Look, No Clues!” Tests
The anti-pattern
Someone’s gone wild with “DRY”. Everything meaningful has been shoved into helpers, utils, factories, decorators, abstract test classes, base fixtures, or whatever other architectural landfill they’ve invented.
I once named a folder Junk Draw and put all the classes I didn't like in there to be refactored out. Sadly, people just thought this was the new place to put "helper" classes. Anyway....
[Test]
public void ShouldHandleOrderProcessing()
{
var order = CreateDefaultOrder();
Process(order);
AssertProcessed(order);
}
This is the sort of thing I have seen people write after a reading a Medium article on “elegance”, or "keeping your tests skinny".
The problem is that we haven’t the faintest idea what’s being processed, what matters, or what the behaviour under test even is. We have to dive through half the repo to figure out what “Process” does and why any of it matters.
2. Good: A Test That Says The Bloody Thing Out Loud
[Test]
public void Processes_order_and_marks_it_as_completed()
{
// Arrange: a draft order with one item worth £25
var order = new Order
{
Id = Guid.NewGuid(),
Status = OrderStatus.Draft,
Items = new List<OrderItem>
{
new OrderItem("Widget A", 25m)
}
};
// Act: submit it for processing
_orderProcessor.Process(order);
// Assert: the order is completed and total cost is calculated
Assert.That(order.Status, Is.EqualTo(OrderStatus.Completed));
Assert.That(order.TotalCost, Is.EqualTo(25m));
}
Longer? Yes. Clearer? Also yes. Ambiguous? Not a chance.
A test like this tells you the rules, shows the data, and makes the intent explicit. Nobody is jumping around through 19 “test utils” to find out why the total is 25 quid.
Recommended by LinkedIn
3. Bad: BDD Steps That Turn Into Performance Art
BDD is meant to make behaviour readable. Instead, about half the industry uses it to hide all logic behind storybook steps. Something like this:
Scenario: Processing an order
Given an order exists
When I process it
Then it is completed
This reads nicely, sure, but it explains absolutely nothing.
I once saw a BDD script that started "Given I am an API...". Occasionally I still wake up at night screaming.
4. Good: BDD With Actual Value
Keep the steps as a facade, that's fine. But let the scenario show the behaviour, not hide it:
Scenario: Completing a draft order with a single item
Given a draft order containing an item costing £25
When the order is submitted for processing
Then the order status should be Completed
And the total cost should be £25
Then implement the steps with simple, readable code:
[Given(@"a draft order containing an item costing £(.*)")]
public void GivenDraftOrder(decimal cost)
{
_order = new Order
{
Status = OrderStatus.Draft,
Items = new List<OrderItem>
{
new OrderItem("Widget A", cost)
}
};
}
[When(@"the order is submitted for processing")]
public void WhenSubmitted()
{
_processor.Process(_order);
}
[Then(@"the order status should be Completed")]
public void ThenStatusIsCompleted()
{
Assert.That(_order.Status, Is.EqualTo(OrderStatus.Completed));
}
The behaviour is upfront. The code behind is simple, not clever. Everyone wins except the tester who wanted to hide everything behind five layers of cucumber magic.
5. A Final Example: Intent Versus Obfuscation
Bad
def test_user_access():
user = make_user()
do_login(user)
assert_access_granted(user)
This proves nothing except that someone enjoyed naming functions like bad WiFi passwords.
Good
def test_user_with_valid_credentials_can_log_in():
user = User(username="alice", password="s3cr3t")
auth = AuthService()
result = auth.login(user.username, user.password)
assert result.success is True
assert result.token is not None
You know who, you know what, you know why.
The Point (I knew I'd get round to it)
WET tests aren’t about being wordy. They’re about being unmistakable.
If the intent is obvious, the behaviour is obvious. If the behaviour is obvious, the bug is obvious. If the bug is obvious, you fix it without wanting to cry into your tea or smash your keyboard against the wall.
Write tests your future self won’t hate you for. The bar is low, but here we are.