DRY vs DAMP in unit tests

Looping through endpoints isn’t bad thing …. or is it?

Benjamin Lo
3 min readJul 9, 2020

Tl:Dr — DRY for production code, sometimes ok in test code, but DAMP is preferrable.

(Disclaimer: Since I use Python 3 & Django on a daily basis i’ll be using snippets from them as examples.)

Since last year up until now i’ve been struggling with a concept of DRY coding. In a nutshell, “Do not repeat yourself”. So if you can find stuff that’s pretty repetitive, then chances are you ought to automate this to a single source. This way when specification changes you can reduce having to do multiple changes.

And this is great for production code. And probably okay for test code right?

Nope.

Well, theoretically (i thought) yes. We just don’t want to repeat whatever bad smell we can find. But as it turns out, this is not always the case and it depends on use cases. One day i ran into a failing test that spat out the below error message:

Which view is failing & passing? Dunno because the message is ambiguous..

The error above is coming from this test:

Loops through a list of endpoints. Can be 3, 9 or even 20 for all we know ..

This test case does what it intends to do from the docstring. But the problem here is I can’t tell which view is passing and which is failing. Adding further to the problem, my list_of_views param looks like a list of dictionaries containing urls and status codes we expect them to have:

A long list of dictionarys contaning our endpoints & expected status codes..

Actually, the problem is that this subTest wasn’t written correctly. And it might be hard to spot too. Because this:

should be written like this:

But even if we can find the problem and approach using DRY, it doesn’t mean our tests will be readable even. Consider this as a possibility:

A test that loops through a list of views via GET request.

Looks pretty intimidating right? This was also reformatted using Flake8 so that it becomes readable. But even still, this is a lot to take in.

Each URL for each view also accepts arguments in their paths so it could look something like https://example.com/view_1/foo=foo&bar=bar

Solution

I asked my boss during a 1:1 meeting about my struggle with this. He suggested the DAMP approach. In a nutshell, use DRY for production code but DAMP is actually ok in unit tests. Because it doesn’t affect your code base at all and you really just want to test something like our views while also making it readable and easier to debug.

With this way, you can tell which view is failing and which is passing

Separated test case for all views using DAMP approach.

So when is DRY acceptable in unit tests?

I’m going to take this example from here because it perfectly demonstrates (in my opinion) when DRY is good.

Loops through all 4xx status codes and checks errors return true

It’s better to use DAMP for stuff like testing endpoints & views. But for small functions, utility methods that require you to (for example) loop through a small collection of valid/invalid data types then yeah that seems reasonable.

Because with this snippet above your test result looks like this:

Line 2 perfectly tells you which status code had failed.

Questions? disagree?

Lemmi know. I’m actively learning new ways to improve the quality of my code for the convenience of my peers also and anytime I can be proven wrong is a big win for me too.

References:

What does “DAMP not DRY” mean when talking about unit tests?

Subtests are the Best

--

--

Benjamin Lo

A Senior Software Engineer with an unquenchable thirst for learning