Late to the party. Please forgive me. Got busy today.
How do you square TDD with non-testable requirements?
You don't. TDD is about testing. It's not about what you don't test.
One of the three rules of TDD, which Bob Martin advocates for in his book, is to never write more code that is necessary to get your tests pass
Right, when you're doing TDD. Not every line of code gets developed and covered under TDD. When you write such code, you're on your own. This is why code coverage tools don't just turn from red to green. They keep telling you your coverage percentage. Which, frankly, isn't the important thing to know.
For one thing, there's no chance in hell you can make a robust test suit for GUI. You will always have to check it with your own eyes. Though, I guess, that is a test in a sense – Sergey Zolotarev
Uncle Bob has talked about that problem and presented the solution. It's called the Humble Object. When grappling with hard to test things, like GUIs or IO aware code, hollow them out so they hold no actual logic. Make them so simple that the only real test they need is a code review. Move the interesting code to somewhere easy to test. Throw your TDD at that.
That right there tells you the important thing. The important thing is knowing which code is under test. You can have 99% code coverage but if that 1% is hiding in some critical hard to read logic I'm going to kill myself covering it, moving it, or eliminating it because that's a nightmare I don't need. But even with coverage of 70%, I'll sleep soundly if I know 100% of that 30% is in logic-free, easy-to-read code. Because reading code is a test too.
This humble object pattern has a close cousin. It's called Functional Core, Imperative Shell. Here we explicitly set aside a part of our architecture for the hard-to-read, easy-to-test business logic and another part for the hard-to-test, easy-to-read configuration code that knows where all that IO/GUI crap is. It's really the same idea. Just makes it easier to tell what goes where.*
* Spring made a mint forcing us to do the imperative shell in XML so we had to separate them.
The rules of TDD are for when you're writing code that you want to be sure is covered with your automated tests. And that's covered all the way down into the little private methods. You don't need to call them directly. Just exercise them.
Following the TDD rules lets you know that what you're creating is covered. They never said you can develop 100% of any app this way.** They said follow these rules and what you create while following them will be 100% covered. That can be very useful to know.
** Since the most testable code is functional code (at least, deterministic & side effect free) they’ve actually proven you can’t write 100% of any useful program with only testable code. You need side effects to communicate with the outside world.
But that strongly assumes each app requirement is translatable into a test assertion which isn't the case
TDD is not, has never been, and never will be, the only way to test.
"When the app is running the computer must smell like cinnamon"
OK, run the app. Do you smell cinnamon?
Not everything is TDD. Not everything needs an automated test. But my god don't pretend code shouldn't be tested just because you can write untestable code.