Loosely Couple Your Tests

Loosely couple your tests from your implementation to allow your implementation to be refactored, without having to change the tests

Introduction

One of the most common set of tests that I see is the upfront naming of one fixture for one class. The difficulty occurs when later on the classes become refactored into other classes and you're left with fixtures named after classes that no longer exist. Perhaps more insidious though is that by having this coupling, you increase the amount of inertia that must be overcome in order to refactor a class. In order to maintain a perceived consistency, you have to rename the test fixtures. Even worse, many current source code implementations refuse to play happily with renaming of files, so the refactoring tools are less effective, and the resistance to change increases again. By decoupling the tests from the implementation, the tests can stand on their own, and the implementation is free to be refactored as needed.

The Practice

Consider a very simple implementation of a Blog Engine. The story is as follows...A blog consists of a number of entries. Each entry must have a title and content. Each entry gets a date created which refers to when the entry was initially drafted, and the date posted which indicates when the entry was published. An entry can be created and added to the blog without being posted - a draft. An entry can be created and immediately posted. An draft can be posted at a later date.

Take 1

A fairly typical implementation would consist of the following fixtures... (after some refactoring).

This leads to two classes - Entry and Blog. This makes sense of course, and the implementation is quite simple and neat. In short, this path of TDD leads to a successful implementation of Blog and Entry, I can post, get the dates, and have rudimentry validation on my blog.

The downside though is that the reader / business analyst / new developer, that picks up these tests has an increased inertia in changing them. The very name of the test fixtures themselves forces an almost subliminal desire to maintain the current Blog / Entry structure, reducing the flexibility and creativity of the developer.

Take 2

Same story, but remove any sort of artificial constructs and just add the tests one after the other refactoring mercilessly. I started with the simplest thing I could think of that provided some behaviour...

After the addition of the second test, which is to assert that the PostedDate is attached to the Blog, the following information arises - one - we need an Entry class with which we can populate the blogs Posted with, and two, we can refactor out the setup of both tests into a setup method and give the fixture a readable name. This covers of the requirements of "number of entries" and "contains a posted date" from the story.

Focusing next on invalid entries leads to the extraction of a simpler setup super class which contains methods to instantiate a blog and provide clean entry classes for testing successful postings, and invalid posting data.

Note that this is now inherently more readable. Posting validation rules have been moved and renamed into a group, as have the rules around successful postings. The last two things to deal with are the default values on creating an entry, and the addition of draft entries to the blog without posting.

The best thing though is that none of the rules or tests are constraining the implementation. The rules stand on their own. Any changes to the implementation through refactoring tools will change the tests, which is a good thing, but they aren't artificially constrained by the tests.

Conclusion

In short - by removing a preconceived structure from the test fixtures, you allow a more organic growth of the code as it adapts to new business rules and constraints. Refactoring mercilessly leads to removed duplication, and frequently, the promotion of "TestHelpers" that create valid objects into product code "Factory" objects. The readability of the tests is enhanced, and even non-developers are able to read them. Finally, the tests are less brittle as you are no longer focusing on forcing the code to fit the test structure, but rather, focusing on how the code solves the behavioural requirements of the tests.

Edits

Updated example call to remove spurious call to Setup(); Unnecessary as the text fixture will run it.

Changed ArgumentNullException to ArgumentException in tests for Title and Content as an empty string is not null - but is instead an invalid Argument. Could also have used out of range, but that implies to me a range of values is expected (1 - 100 for example) - thanks to Joshua McKinney for these.

Nice spot. Something to be alert to for refactoring setup tests. I've removed it - thanks.

Insidious. A test that would never fail. ArgumentNullException in it's current state was a useless test. I've since gone back and checked the "real" code, and the same error existed. Sure enough - when I tested my tests - it failed to fail. Lesson learnt.

I've edited the article and made a note at the footer crediting you. Thanks!

It is exactly right point. I've seen so many times developers testing their classes. But such tests do not represent any value. The subject of unit testing is not a class but rather a logical software component with it's distinctive functionality.

The functionality of a class is irrelevant as class may or may not represent a component, in fact some components may be implemented in more than one class.

The easy way out is to do the design first and fit the tests, and that's what many people do when they first start. That's not a bad thing either, at least it's getting them tested. It's also very common when trying to introduce TDD.

The next time, people try and write the tests first, but it's still done with a focus on a preconceived design. The result is you constrain the tests and therefore the design.

When you've done TDD for a while, you soon realise that in the above two cases the tests become a barrier to the natural evolution of code. The net result is that the tests become brittle, and the code change hurt increases. Trying to decouple the tests really gives you significantly more understanding of the problem domain which can only be a good thing.

Just FYI, there's a book called xUnit Test Patterns: Refactoring Test Code with alot of similar ideas, a good read. Keeping tests easily readable seems to be the easiest goal to shoot for which means you refactor lots of stuff into helper functions which can easily be reused and you end up with almost a domain language in your tests.