Test Driven Development (TDD) is not a design. It's a requirement that impacts your design. Just as if you were required to be thread safe, that's not a design. Again, it's a requirement that impacts your design.
If you gleefully ignore all other design concerns and religiously keep to the TDD rules don't blame TDD when your code turns into crap. It will be testable crap but it will be crap.
One nice thing about testable crap is that it's refactorable crap so for some people that's good enough. We'll get fancy only when needed. Others hate this and blame TDD for it. No. This is your doing.
Domain Driven Design (DDD) is something you do before TDD's red green refactor cycle.
DDD is the effort to create and preserve a space in the code where a domain expert, who is largely oblivious to the details of the system, can understand how to control the system. This is done by abstraction and modeling a problem domain in a familiar way.
A DDD system can have an architecture that looks like this:
This DDD architecture goes by a lot of names: Clean, Onion, Hexagonal, etc
Here's the disconnect I see many people have when they look at this design. This isn't concrete. I can follow this design and never have written anything you see diagrammed here. I see others insist there must be a use case object or an entities class. What these are is a set of rules that tell you who you can talk to and how.
That's it. Follow the rules of this design and you can TDD your little heart out. TDD doesn't care who you talk to. It cares that everything that does something can be proven to work or not at the click of a button. Not, something somewhere is broken. It tells you exactly what's broken.
Still to vague? Look at the Controler - Use Case Interactor - Presenter diagram in the lower right corner. Here are three concrete things communicating with each other. Sure this is DDD but how do you add TDD here? Just mock the concrete stuff. Presenter must be receiving information. A PresenterMock class would be a good way to check that it's getting what you expected it to get. Hand the Use Case Interactor the PresenterMock and drive the Use Case Interactor as if you were the Controller and you have a nice way to unit test the Use Case Interactor since the mock will tell you if it got what you expected it to get.
Well look at that. TDD satisfied and we didn't have to futz with our DDD design. How did that happen? We started with a well decoupled design.
If you use TDD to drive design (not simply Development) you get a design that reflects the effort you put into it. If that's what you want fine. But that was never what TDD was meant for. What this ends up lacking is certainly not TDD's fault.
TDD is not about design. If you have to make design changes to use TDD you have bigger problems than testing.
