Some proponents of TDD say, that TDD forces you to find a good design. Some even translate TDD to Test Driven Design. I don’t agree. Mostly.
Lets start with the small part where I do agree.
Since TDD forces you to write test first, it forces you to think about how you want to use an API, because that is the very first thing you write down. This is a good thing and in my case led to more usable APIs (often in the form of Builders or little internal DSLs).
Since it is almost insane to test anything if it isn’t properly decoupled from stuff it uses, TDD also forces you to factor out dependencies in order to be able to mock them. Thats the other point where TDD encourages good design.
But there are bad news: There is more to software design than just nice to use APIs and SOLID principles: Your design actually has to solve a problem! And TDD does almost nothing for you in order to find a good solution for your problem. Yet this is really the hard part in software design.
Can your domain model represent all the cases needed by the business? TDD won’t tell you.
Should represent some edge cases of the domain in a ‘dirty’ way and thereby making the domain model simpler and therefor 99% of the use cases way easier? Or should you go for the complete but awfully complex solution? TDD won’t tell you.
Which of all the design patterns would help you? Or is there some well known algorithm you could use to solve your problem? TDD won’t tell you.
Does the problem become trivial once you approach it in a functional way? TDD won’t tell you.
Is recursion a solution or a dead end? TDD won’t tell you.
You have to understand the problem and know lots of possible approaches to a problem in order to design software well. There is just no way around it.
Once you have the solution in your mind, once you know the basic data structures and algorithm you going to use, TDD will help you to implement it in a clean, well factored way. It will also help you to stay focused on the task at hand (which is making the next test green).