This is the 3rd part of the series about analyzing what we’re coding, and therefore what to test. Last time, we talked about test categorization and design considerations regarding testability.
We haven’t touched or written any code yet. Nor will we before the end of this post. Patience is a virtue in both reading and writing tests.
Step 5: Identify design constraints
In TDD we assume that that we’re starting from scratch and we can do what ever we want, because there’s no code to get in our way. But that is not the always the case.
Imaging we have a library of services, and we’re adding a new one. Every service derives from some BaseService class.
Lo and behold :We’ve got ourselves a design constraint. We’ll need to use a specific interface. We might even need to contend with a common implementation that already exists, that may or may not clash with a design that could have emerged from the tests.
That’s one example. Here’s another, that you can’t know about in advance.
The code we’re designing is a component, a part of a bigger design, and there are other developers working on the other components. We can define interfaces up front, and put the constraints in place. Those may change in the future as all the team members make progress, and learn about dependencies between the components. We’ll only know in the end if the interfaces we’ve designed were loose or tight. Regardless, as we’re developing we constrain ourselves (and the tests) with them.
Emergent design is not what it’s all cracked up to be, is it?
Things are worse off in test-after. The solution is now hard coded, and we still want to test it according to those test cases we said we’d write. The considerations for testability should eventually mesh with our current design. So, if the current design is testable enough in its current form, we’re fine. If not, we’ll need to do some work. Usually for unit testing, it means extracting the logic we want to new, internal modules we can test in isolation, and keep existing interfaces and dependencies tested at the integration level.
Once we accept the constraints, we need to navigate our way to the tests with them in mind.
In the next step, we’re going to examine the code more closely.