TDD is great for some types of code, where the code is mostly self-contained with few external dependencies and the expected inputs and outputs are well defined and known ahead of time.
TDD is miserable for code that is dependent on data or external resources (especially stateful resources). In most cases, writing "integration" tests feels like its not worth the effort given all the code that goes into managing those external resources. Yes, I know about mocking. But mocking frameworks are: 1 - not trivial to use correctly, and 2 - often don't implement all the functionality you may need to mock.
I completely agree. I'll use TDD when implementing a function "where the code is mostly self-contained with few external dependencies and the expected inputs and outputs are well defined and known ahead of time" and where the function is complex enough that I'm uncertain about its correctness. Though I find I usually do property testing, or comparison to a baseline on random inputs, similar to the quicksort example in the blog post (against a slow, naive implementation of the function; or an older version of the function, if I'm refactoring) rather than straight TDD.
When debugging, I'll also turn failure cases into unit tests and add them to the CI. The cost to write the test has already been paid in this case, so using them to catch regressions is all-upside.
System tests are harder to do (since they require reasoning about the entire program rather than single functions) but in my experience are the most productive, in terms of catching the most bugs in least time. Certainly every minute spent writing a framework for mocking inputs into unit tests should probably have been spent on system testing instead.
I would go so far as to say that if your code requires extensive mocking to be tested at a certain level of granularity, then unit tests are the wrong choice at that level. Every time I've seen a test suite with abundant mocking, it was mostly testing the mocks - numerous tests get broken by the slightest refactoring and require attention, while real-world integration bugs fly straight through.
IMO functional and integration testing should be where effort is spent first and foremost. If there are resources beyond that, then do finer grained unit testing, but even then closely tracking the amount of time spent on all the scaffolding necessary for it vs the benefits for what gets tested.
>...TDD is great for some types of code, where the code is mostly self-contained with few external dependencies and the expected inputs and outputs are well defined and known ahead of time.
I find that TDD is very well fit to fix the expectations from the external dependencies.
Of course, when such dependency is extensive, like an API wrapper, then writing equally extensive tests would be redundant. Even then, the core aspects of the external dependencies should be fixed testably.
Testing is a balance game, even with TDD. The goal is to increase certainty under dynamic changes and increasing complexity.
TDD is miserable for code that is dependent on data or external resources (especially stateful resources). In most cases, writing "integration" tests feels like its not worth the effort given all the code that goes into managing those external resources. Yes, I know about mocking. But mocking frameworks are: 1 - not trivial to use correctly, and 2 - often don't implement all the functionality you may need to mock.