TDD is about design. It’s not about testing.
Last week an interesting thing happened while I was working with a team. They had been writing a new application with TDD since last June, and they were successful in that the application was nearly ready, on time and on budget. But there was one thing that was worrying them.
They had been following an article on the Model-View-Controller pattern, and had structured their application accordingly. There was a controller for every window, and there was a model too. (It’s not a web application, it’s Swing.) The view object was extremely thin, as it only contained code for constructing and laying out widgets; absolutely no other logic in there. The controller hooked listeners to the appropriate UI widgets.
So everything was OK, no? A clear architecture, a clear separation of concerns, everything tested. There was a problem: the controllers were big objects, with intricate logic. The tests were difficult to read and difficult to write. It was difficult to understand what was being tested. My worry was that maintenance of that GUI could quickly turn into a mess. Also my customer was worried about that. The fact is, whenever anything had to be changed about the logic of that window, the controller would have to be changed, and the view would have to be changed, and the model would have to be changed. We had three big monoliths that were involved in everything. This ran against the open-closed principle.
My first reaction was to call upon my refactoring skills. Take the unreadable tests and make them readable. Use extract method and expressive names to make them read like a story about what was being tested. Some success there, but not a clear victory. It was still difficult to find the code being tested, as those were end-to-end tests. Then I found the bit that contained the important logic and wrote proper unit tests for it. Some success, as I was able to test with precision all the corner cases that the end-to-end test was not exercising. But still not a clear victory. Then I started to work on the big controller. I took the worst one with me in my hotel room, and I spent a chunk of my evening trying to refactor it. Extract method, rename, extract class. Extract, extract, extract. It was still big and ugly. With a bit of polish, but still big and ugly. Still running against the OCP.
The next day I pulled a different set of tools. I remembered the lessons in Francesco Cirillo‘s Emergent Design Workshop. I showed the team how to use Francesco’s technique, which is to start with a design of the communicating objects on the whiteboard. After one day of practice with this technique, we attacked the real problem. We started designing the main application window from scratch.
The team was surprised, and I was surprised as well, because the big controller evaporated. It was clear, when working at the whiteboard, that it was not necessary. The model object evaporated too. We designed the main scenarios, and we saw that a simple design could accommodate all of them. At first I sketched the design for them, but they were soon criticizing my design and changing it to suit their needs and tastes. Then we started coding; it went smoothly because the design was already done, and we were taking advantage of all the low-level objects that they had implemented already.
We learned is that when the design is wrong, no amount of refactoring moves will get you out of the hole.