Last time we’ve started talking about how to approach TDD in real, complex systems. While plain vanilla TDD assumes nothing about the world around our component, in real life, there are constraints we need to relate to.
Where we stopped, we talked about rethinking the order of work, based on all kinds of knowledge we’ve gathered. Let’s continue.
Step 5 – Component decisions
Not going into the emergent discussion again, there is still room for some upfront design. Before we start coding we can make additional design decisions. For example, if the stories go through an existing component, one that is buggy and doesn’t have any tests, we need to do something about it. If we just patch on code (TDD or not), there is still risk of breaking existing functionality. We can decide to wrap this component in tests, rewrite it, or build around it. This is not a design decision we’ll make as part of the actual TDD cycle, but it has an impact on how our component interacts with the rest of the system.
Other decisions we might make are if we’re not satisfied with the current design (are we ever?)
We can think about breaking down big modules or combining small modules, as part of the work we’ll do. This is architectural refactoring – there are changes we plan to do in the architecture, without modifying existing functionality. We’re doing this because we want to get a sense for where we’re going. We want boundaries to guide us. Constraints are helpful, remember?
Step 6 – Interface decisions
We can also start identifying which connection points to use. While this is not a full interface definition between components (it’s a bit early for that), we can decide on reusing existing interfaces, adding or modifying them. Of course, if all the code is new, we can define whatever we want.
In TDD, we define the interface of our components through tests. If the interface depends on usage by existing consumers, this has an affect on our design.
We can agree on interface contracts if these can be “managed”. Why the quote marks? When we discover we need changes to the interfaces (and we will), they should be easy to change – meaning, not too much work, or not introducing big risks. We don’t want to define interfaces that take weeks to re-discuss, or re-test. If the interfaces are defined within a team, these are easily managed, so go ahead. If not, make sure not to close the definition too early.
Step 7 – Write the code
Finally, we’re getting to the coding bit.
Remember, we’re doing it by the book. We’ll write just enough code for the story. That’s right – not a whole component at once. If you can use TDD at this point, great. Just stop when you get to the end of what the component needs to do for that story alone. If test-first won’t work (for any reason) make sure you write tests when appropriate.
The worst we can do is to write the components “fully” for all the stories. What we’ll end up with is “mostly-working complete “components.
Step 8 – Integrate continuously
I don’t mean that in the CI-tool sense. Make sure you are always integrating the whole story to see how it works. You’ll learn what works, and what needs to be changed. Then you adapt.
Since you and your team are focused on only one story, there’s a lot less code to integrate. The integration is far less complex than integration of multiple, full-bodied components.
Obviously, we alternate between steps 7 and 8 until…
Step 9 – Complete the story and move to the next
“Complete”means having some proof that it – the entire story – works. The proof should come in the form of an automated end-to-end test and a product review in order to get feedback. This feedback can (and will) change things, in terms of what comes next.
Then again, the waste is not going to be big if we built just enough for the first story.
And that’s the important thing. Every code we have is a liability. It can contain bugs, it will require change and we’d like to write as little as we can of it, just to make sure we’re on the right track. TDD helps us with the design and with creating a risk mitigation (in a form of a regression test suite) that will help with the upcoming changes.
All the upfront thinking, considerations, identifying constraints and boundaries – they are all helpful in using TDD in the right context. Otherwise we’ll be building the wrong thing right. We don’t want that waste.