Santiago Palladino

Santiago Palladino

Tests are also Code

6 min
May 18 2009
6 min
May 18 2009

Many times we put all of our effort in developing an elegant solution to a certain problem we have, thus leaving testing to a second place, without too much consideration for it. Regardless of being using TDD or just creating unit tests for the already-written code, keeping tests as clean as the code should also be a major goal.

Why do we bother coding “elegantly” in the first place? We don’t we just write the first chunk of code we come up with, as long as it works, but rather take great care in refactoring it?

Well, after years of seeing code smells, I bet anyone can give more than a single reason for that. Code must be easily modifiable, because a requirement might change, an environment constraint might change, or simply because we did not get it right the first time. Code must be able to grow, to accept new functionality without breaking the existing one. Code must be declarative and easy to understand by another developer; note that the same developer who coded it a month ago qualifies as “another developer”. And above all, code must be beautiful.

Now think of these attributes applied to testing. If a client changes a requirement, we will probably have to change the underlying logic. Hopefully it won’t affect many components, as we have spent much effort refactoring it to ensure concerns are properly separated.

And then we go to the tests to see which ones should change, and all hell breaks loose. A large fixture with undeclarative tests makes it nearly impossible to spot which functionality is being tested where. And if you actually can understand it, modifying it will be as painful as modifying the original components if no patterns were kept in mind.

Think of something as innocent as adding a new dependency to a service in a certain module. The system should not be affected, as you are probably using a Factory pattern, if not a whole dependency injection framework, to manage the initialization of components. Therefore the change is limited to a single piece of code.

And then you get one error per test that initializes the object. Somehow copy-pasting code does not seem as harmful as it should be. I strongly believe that whenever any programmer presses Ctrl+C on a piece of code, the computer should electrocute him instead of copying the code to the clipboard. And please don’t ask my believes regarding copying code using the mouse’s right click.

But back to the problem. Test fixtures do have a setup, and it is there for something. Use it! If there is no common initialization code for all tests, then extract all you can to helper methods, so you isolate and reduce object creation as much as possible. This principle should be familiar to you, as it is the same you apply to the very system you are testing.

Object creation is just a very particular problem. Most of the frequent problems that affect code also affect tests. Some of them even affect them more critically, as a test should be even more declarative than the code being tested (it is supposed to be simpler, isn’t it?).

To address those frequent problems, some folks out there came up with something called Design Patterns. The natural conclusion to testing problems was exactly the same. I strongly recommend reading the text, and realize at least how many testing-code smells you are making.

Remember, the next time you write a test, make sure you put into it the same care you put on the code being tested.

Adding comments in a test won’t kill anyone!!