What cooking can teach to a software developer
Let's start with question which does not really matter for software development: how much flour should I put in my pastries?
The complex answer is that it should be just enough for making them stiff and not squashing down, and also increase in size in the oven. If it is not enough, the dough would be liquid and unusable; if it is too much, the products will be hard to bite or accompanied by a strange taste.
The simple answer is instead, there is a quantity in the recipe found on the internet, follow it.
While the subject of this question is oblivious to most of us, its format is typical in the field of software development. It is a short question, which can be answered in a short form by making many assumptions; these assumptions will inevitably stop to work when the context change, or we have to face an even slightly different scenario.
What if my oven does not have a thermostat, since it uses natural gas? Or if I have no butter in my fridge, can I always use margarine instead? For a novice cook, these are every day questions, as for a novice programmer the question of how many tests should he write returns often.
The researches on the topic of knowledge and expertise have come to the following conclusions: novices use recipes. Experts use their judgment, and know that recipes so well that they can change them on the fly and get a better result. The later stages of expertise are characterized, between other things, with the absence of reliance on recipes. And when a recipe is followed blindly or just when it is comfortable to do so, the result is in fact called metaphorically an half-baked solution.
Let's talk about TDD instead of pastries now.
Its recipe (beginner level) is very simple: red, green, refactor. First you write a test, second you make it pass, then you can modify the code before it solidifies.
At an intermediate level however, you can mix the TDD recipe with more advanced ones; for example, you can first write a test at higher level (end-to-end or for a component), then the lower at the unit level for classes or methods.
At the expert level, you'll know that the recipe falls short sometimes. Sometimes you need to write a bit of code first, to build a DSL for your tests (in Selenium-powered tests for example). Only then you can start to go test-driven. And you shouldn't write tests for getters and setters unless they have a particular behavior: they will be indirectly tested and used from coarser grained functional tests. And tests in isolation go hand-in-hand with design practices like Dependency Injection and sometimes Double Dispatch, which make these tests fast and possible at all.
Sometimes you want only smoke tests, not really detailed ones. And what if to make a test pass, a previous one fail? It is acceptable, and you should fix A, or you should try to make B pass in another way? What happens when a test suite cease to work at a random point? How do we debug this?
And if a test passes when run alone, but fails in the middle of the test suite? How do we find which is the one that clashes with it? What is the underlying problem (global state at work)?
I won't go on with other TDD caveats, but the point is clear: recipes can only go to a certain length, while when every specific situation needs an adaptation of the recipe, as well as things that go wrong need a reaction from the developer.
Stop reading, start cooking
In fact, there is no full list for the TDD caveats: to learn them you have to make experience and write more than one thousand tests, of many different types.
Just like a real chef can only learn and improve through cooking, not by reading books or learning recipes. A cookbook does not make you a cook, nor a programmer: books are only proficient if you apply what the author explained you in those 400 pages.
Each time I make my pastries, they are better than the previous time, but the most important thing is that I learn something new (and when I stop learning, I should try to cook something else.) It may be how much they dilate, or how to balance the ingredients better, or to respond to an unforeseen event and avoid a disaster. And each time I write a new test, I learn something new about how to efficienty write test code, and how to design production code. Taste your code, and try to actively improve it instead of going on with the auto pilot.