I don’t mean just fewer errors, although that is definitely a happy side effect of TDD. I mean that the code is easier to read, maintain and has fewer leaky abstractions.
Here’s a recent anecdote:
I was working on an existing code with limited unit testing and what tests that existed were often poorly expressed ( it was my own code, so I’m not blaming anyone other than myself here; it’s a learning thing). My work involved implementing new tests and re-expressing existing tests so that I could confidently make some larger scope changes to the code.
As part of the refactoring I began implementing mocks to simplify the tests and have them focus on the precise desired behaviour; this process had the side effect of making the tests more readable, easier to understand. Yay! While working on a particular class I subsequently I became aware that I was implementing large numbers of mocks at quite low levels of detail and I realised that this was a code smell; if I’m having to mock low level details that are essentially irrelevant at my current level of abstraction then there is a good chance that I have a leaky abstraction. Implementing some “extract class” refactorings and now I have a much cleaner, simpler set of tests on this class.
Another aspect of this is that the low level mocks I was implementing masked out different behaviours in the class so that I couldn’t confidently test all the behaviours. This was another code smell that my class was trying to do too many things. By doing the “extract class” refactorings I was also able to isolate the various behaviours and test them independently. Very useful.
In the context of TDD, hopefully you can see that if I had originally written those tests before I wrote code (or during the TDD process), then I would have started with a cleaner abstraction rather than having to refactor it in; much easier to maintain code up front.