One difficulty I have with this is that, while defining everything upfront like this works nicely in well-understood domains (such as implementing pre-established business logic), elsewhere my understanding of the problem only really forms through writing code and seeing what approaches work.
Normally I try to keep code minimal and easy to change to start with so I can quickly refactor, and then stabilize with proper tests/documentation/interfaces/extensibility as other things start relying on it. Maybe this should be a separate prototype phase, which I throw away before starting the proper TDD production version? But I'm not sure if it's usually cleanly demarcated enough for that.
> Of course, this assumes that the code works right now, but why wouldn't you assume that?
I feel TDD has to some extent the same problem in the opposite direction: you're writing code to pass the test, so the test becomes less useful at finding potential errors and logic issues with the tests may be copied over to the code.