I ruined several unit tests some time ago when I went through and refactored them to make them more DRY--the intent of each test was no longer clear. It seems there is a trade-off between tests' readability and maintainability. If I leave duplicated code in unit tests, they're more readable, but then if I change the SUT, I'll have to track down and change each copy of the duplicated code.

Do you agree that this trade-off exists? If so, do you prefer your tests to be readable, or maintainable?

11 Answers
11

Duplicated code is a smell in unit test code just as much as in other code. If you have duplicated code in tests, it makes it harder to refactor the implementation code because you have a disproportionate number of tests to update. Tests should help you refactor with confidence, rather than be a large burden that impedes your work on the code being tested.

If the duplication is in fixture set up, consider making more use of the setUp method or providing more (or more flexible) Creation Methods.

If the duplication is in the code manipulating the SUT, then ask yourself why multiple so-called “unit” tests are exercising the exact same functionality.

If the duplication is in the assertions, then perhaps you need some Custom Assertions. For example, if multiple tests have a string of assertions like:

Then perhaps you need a single assertPersonEqual method, so that you can write assertPersonEqual(Person('Joe', 'Bloggs', 23), person). (Or perhaps you simply need to overload the equality operator on Person.)

As you mention, it is important for test code to be readable. In particular, it is important that the intent of a test is clear. I find that if many tests look mostly the same, (e.g. three-quarters of the lines the same or virtually the same) it is hard to spot and recognise the significant differences without carefully reading and comparing them. So I find that refactoring to remove duplication helps readability, because every line of every test method is directly relevant to the purpose of the test. That's much more helpful for the reader than a random combination of lines that are directly relevant, and lines that are just boilerplate.

That said, sometimes tests are exercising complex situations that are similiar but still significantly different, and it is hard to find a good way to reduce the duplication. Use common sense: if you feel the tests are readable and make their intent clear, and you're comfortable with perhaps needing to update more than a theoretically minimal number of tests when refactoring the code invoked by the tests, then accept the imperfection and move on to something more productive. You can always come back and refactor the tests later, when inspiration strikes!

"Duplicated code is a smell in unit test code just as much as in other code." No. "If you have duplicated code in tests, it makes it harder to refactor the implementation code because you have a disproportionate number of tests to update." This happens because you are testing the private API instead of the public API.
– user11617Jan 18 '13 at 14:39

8

But to prevent duplicated code in unit tests you usually need to introduce new logic. I don't think unit tests should contain logic because then you would need unit tests of unit tests.
– Petr PellerDec 27 '15 at 19:04

@user11617 please define "private API" and "public Api". In my understanding, public Api is the Api that visible to external world / 3rd party consumers and explicitly versioned via SemVer or similar, anything else is private. With this definition almost all Unit tests are testing "private API" and hence more sensitive to code duplication, which I think is true.
– KolAApr 9 at 22:38

Readability is more important for tests. If a test fails, you want the problem to be obvious. The developer shouldn't have to wade through a lot of heavily factored test code to determine exactly what failed. You don't want your test code to become so complex that you need to write unit-test-tests.

However, eliminating duplication is usually a good thing, as long as it doesn't obscure anything, and eliminating the duplication in your tests may lead to a better API. Just make sure you don't go past the point of diminishing returns.

@seand You can try to explain what your assert is checking, but when it's failing and contains somewhat obscured code then developer will need to go and unwind it anyway. IMO It's more important to have code to be self-describing there.
– IgorKOct 6 '12 at 23:24

1

@Kristopher, ? Why is this posted is community wiki?
– PacerierDec 3 '15 at 23:57

@Pacerier I don't know. There used to be complicated rules about things automatically becoming community wiki.
– Kristopher JohnsonDec 4 '15 at 0:10

For readability of reports is more important than tests, specially when doing integration or end to end test, the scenarios could be complex enough to avoid navigating a tiny bit, it is okay to find the failure but again for me the failure in the reports should explain the problem good enough.
– AnirudhJun 6 '18 at 7:04

Implementation code and tests are different animals and factoring rules apply differently to them.

Duplicated code or structure is always a smell in implementation code. When you start having boilerplate in implementation, you need to revise your abstractions.

On the other hand, testing code must maintain a level of duplication. Duplication in test code achieves two goals:

Keeping tests decoupled. Excessive test coupling can make it hard to change a single failing test that needs updating because the contract has changed.

Keeping the tests meaningful in isolation. When a single test is failing, it must be reasonably straightforward to find out exactly what it is testing.

I tend to ignore trivial duplication in test code as long as each test method stays shorter than about 20 lines. I like when the setup-run-verify rhythm is apparent in test methods.

When duplication creeps up in the "verify" part of tests, it is often beneficial to define custom assertion methods. Of course, those methods must still test a clearly identified relation that can be made apparent in the method name: assertPegFitsInHole -> good, assertPegIsGood -> bad.

When test methods grow long and repetitive I sometimes find it useful to define fill-in-the-blanks test templates that take a few parameters. Then the actual test methods are reduced to a call to the template method with the appropriate parameters.

As for a lot of things in programming and testing, there is no clear-cut answer. You need to develop a taste, and the best way to do so is to make mistakes.

I'm more tolerant of repetition in test code than in production code, but I have been frustrated by it sometimes. When you change a class's design and you have to go back and tweak 10 different test methods that all do the same setup steps, it's frustrating.

I'm more likely to refactor duplicated code for setting up state. But less likely to refactor the part of the test that actually exercises the code. That said, if exercising the code always takes several lines of code then I might think that is a smell and refactor the actual code under test. And that will improve readability and maintainability of both the code and the tests.

I think this is a good idea. If you have a lot of duplication, see if you can refactor to create a common "test fixture" under which many tests can run. This will eliminate duplicate setup/teardown code.
– Outlaw ProgrammerSep 24 '08 at 21:34

Jay Fields coined the phrase that "DSLs should be DAMP, not DRY", where DAMP means descriptive and meaningful phrases. I think the same applies to tests, too. Obviously, too much duplication is bad. But removing duplication at all costs is even worse. Tests should act as intent-revealing specifications. If, for example, you specify the same feature from several different angles, then a certain amount of duplication is to be expected.

I don't think there is a relation between more duplicated and readable code. I think your test code should be as good as your other code. Non-repeating code is more readable then duplicated code when done well.

I feel that test code requires a similar level of engineering that would normally be applied to production code. There can certainly be arguments made in favor of readability and I would agree that's important.

In my experience, however, I find that well-factored tests are easier to read and understand. If there's 5 tests that each look the same except for one variable that's changed and the assertion at the end, it can be very difficult to find what that single differing item is. Similarly, if it is factored so that only the variable that's changing is visible and the assertion, then it's easy to figure out what the test is doing immediately.

Finding the right level of abstraction when testing can be difficult and I feel it is worth doing.

"refactored them to make them more DRY--the intent of each test was no longer clear"

It sounds like you had trouble doing the refactoring. I'm just guessing, but if it wound up less clear, doesn't that mean you still have more work to do so that you have reasonably elegant tests which are perfectly clear?

That's why tests are a subclass of UnitTest -- so you can design good test suites that are correct, easy to validate and clear.

In the olden times we had testing tools that used different programming languages. It was hard (or impossible) to design pleasant, easy-to-work with tests.

You have the full power of -- whatever language you're using -- Python, Java, C# -- so use that language well. You can achieve good-looking test code that's clear and not too redundant. There's no trade-off.