Some time ago I read a good article on some common TDD anti-patterns by James Carr (which lives right here.) Great, yeah, those are some anti-patterns. So what about patterns?
I wouldn’t say what follows are necessarily TDD patterns; they’re simple techniques for structuring your unit tests. TDD is a great design methodology and it’s absolutely essential to have a robust test portfolio as a refactoring safety net. That said, you are incurring more care and feeding debt simply because you now have more code to maintain.
A well chosen strategy for testing unit tests can ease this burden…
Test Method per Feature
The word feature could easily be substituted for requirement, use case, or user story. This strategy is useful for keeping features granular. As you generally want to keep your unit tests fairly small, this will force you to keep your features fairly small. Agile practices advocate keeping your units of work fairly tiny, e.g. limiting the size of your backlog items or users stories. Having a test method per feature is one way keeping size of test, feature, and business requirement in lockstep.
Test Methods as Examples
Peter Provost and Brad Wilson mentioned in a talk I attended that they had taken to calling TDD example driven review (if I am remembering correctly). The idea is that your tests become specifications of how to use the classes of your app in consumer code. A nice advantage of this approach is that tests become great starting points for developers new to a particular codebase, a kind of developer documentation. You’re feeding two birds with one seed, and that’s always a wonderful thing.
This tactic makes a lot of sense when applying the TDD practice; remember it is fundamentally a design methodology that produces tests as a positive side-effect that aid in refactoring (feeding back into the design). In some regards I wish TDD had called Design By Test, DBT… but that’s another post.
This strategy works well with the test method per feature strategy above. You’d start your test fixture with a comment block that enumerates the list of features encapsulated by the unit under test. As you go through the list, follow the usual red-green-refactor process of creating test methods, writing the implementation, and tweaking the design to satisfaction. As you go, you remove list items from the comment block.
I like this strategy very much. When doing tests as examples I like to prefix my test class with Example.
Test Fixture per Feature
In this scenario you might have a fairly beefy feature that can benefit from multiple tests or you have a feature that is distributed across several units (classes). Generally your test fixtures (classes) will have a one-to-one mapping to a corresponding class (the class under test), but it is occasionally beneficial from an organization and maintenance perspective to break out more complex features into their own fixture.
Test Fixture per Asset
In a domain model adhering to DDD patterns you’d have a test for each Aggregate, Service, Repository, and stand-alone Factory. We use this strategy at Xclaim for our domain model layer. This brings up a larger issue of asset-based development that’s well beyond the scope of this post. I will briefly mention that I like to conceive of the systems I build as “Lego Kits”, and look to conceptually differentiate the red bricks from the green bricks from the mini-figs.
Test Library per Layer
Model your test libraries by architectural layer. This could mean a single test library for multiple assemblies: as long as those assemblies serve the same architectural layer (e.g. data access layer, presentation layer).
Test Method per Defect
In this strategy, you would create a test method when an end user or the test team reports a defect. Typically this will happen after you’ve debugged and diagnosed the defect in question. The idea here is to ensure that the defect doesn’t rear its ugly head again and that you’ve given yourself some “refactoring insurance” as you evolve the codebase.
Inline Tests
Write your tests in the same project as your production code. Yes, this is one way to go, but I don’t like this strategy; it’s too mixed up and hard to manage. If you were to go this way you’d probably want to strip test classes from your automated builds (at least when building in release mode). This requires hand tuning (e.g. you couldn’t ms-build or nant build right from a project/solution file. Even though I’m not a fan, I thought I’d include this strategy as someone out there might be using it and have some insight on benefits/techniques.
—
This is by no means an exhaustive list, but I thought I’d try and start a conversation:
- What tips do you have for structuring your test libraries?
- Are stale unit tests an issue in your shop?