Recently, I have watched a series on cleancoders.com where Robert 'Uncle Bob' Martin and Sandro Mancuso each develop the same application with two different approaches.
The approaches are not directly about TDD, but more about how to develop. Sandro Mancuso went with the 'London' approach of working outside-in from the API specification implementing controller logic and developing the inner workings of the application from there.
Bob Martin used the 'Chicago' approach of inside-out development of the inner domain and working towards the controller from there.
Sandro used the Mockito mocking framework extensively whereas Uncle Bob even abstracted the request handled by the Spark framework used in the application.
Pros and cons
Each of the methods have their benefits and disadvantages. What struck me when watching the series was how much more I liked the end result of Sandro Mancuso's approach. It was a structured step by step implementation of functionality, one endpoint at a time, backed by tests and without major refactoring or fundamental changes in the application apart from what was needed to add the next functionality.
Uncle Bob, on the other hand, abstracted and implemented much more functionality in order to be able to test without using a mocking framework. He still used mocks in his tests, but they were mocks created by himself.
It is fair to mention that the API specification and frontend application used for viewing the progress, was developed beforehand by Sandro, so of course he had the benefit of knowing both the Spark framework and the thoughts behind the API specification.
However, even when considering these advantages, it was clear to me that the application developed by Sandro Mancuso was simpler, more consistent and more clearly tested.
Applying to my own work
When reading the above introduction, you would be forgiven for deducing that I would be totally on board with the textbook TDD approach about writing tests first before implementing anything.
You would be wrong, though.
Not that I would neglect writing the tests and not because I would write the tests after all the code, but because sometimes, I want to explore and experiment with the code structure without constantly having to rewrite my tests.
If I have a clear idea about the API specification, I may have a high level test for the endpoint, but the TDD method of writing a test, implementing the code to make it pass, then refactoring and then writing a new test that fails, seems to me to be restricting my creativity.
With a high level API test, I can quickly get to a green test and then refactor until I am happy with the implementation - and THEN I will do the unit tests to cover all scenarios. A handful of unit tests will be natural during the refactoring, but my focus is on ensuring that the bigger picture works and unit tests are only there to give me the confidence that complex and/or crucial elements work as expected.
Ensuring the code is covered by tests
The final step of covering all scenarios is a vital step. I have previously given my comments on using code test coverage as proof of all scenarios being covered and I still hold that code test coverage percentages alone is a dangerous simplification. However, it is an important part of the key to success: Mutation testing.
I will cover mutation testing in my next blog post.