Way stronger than I'd write, and I don't agree with it entirely, but the author has a point.
Personally, what annoys me the most about TDD I've seen in the wild are two things: designing for tests instead of actual problems, and tests affecting the structure of "real" code.
Designing for tests - the standard TDD approach, first we write tests, then we write code to pass the tests. Quite often the consideration of the problem being solved disappears. It's a fine approach when your task is to write a small black box that takes some data in and transforms it into something else. But I've never seen a case where someone made it work for complex tasks. It always ends up the same - your tests become more complicated than the tested code. It happens with any non-trivial problem, because the thinking you have to do to write tests that make sense is the same as the thinking you need to do to solve the problem in the first place. So you're basically writing the program twice, only in a convoluted way, and without considerations for global design.
Tests affecting the structure - this is IMO a strong code smell. If you're modifying your design to accomodate for tests, by e.g. adding superfluous dependencies, hooks or injection points, you've screwed up. It only makes the code more complicated and less reliable.
The only tests I've found valuable so far (in terms of effect for effort spent) are regression tests - the ones you write to catch bugs in order to make sure they won't happen again. Everything else in TDD seems to be easily replaceable by proper iterative programming.
Maybe that's why TDD is popular in the languages without a sane REPL.
> Tests affecting the structure […] is […] a strong code smell
On the other hand, code being hard or impossible to test is often thought of as a code smell as well. Code that is easy to test is easier to understand — not in the least because there are tests demonstrating its use. Techniques such as dependency injection help a lot here.
Techniques like dependency injection can be really useful -- see Angular -- but too often I see a perfectly understandable piece of code expand into a mess of multiple constructors (only one ever called in production) and helper methods all so that strict unit testing can be done. The post's commit story was unsurprising. If TDD is being done this mangling often just happens upfront. DI is great in the same way interfaces are great, but if your code actually just cares about a particular implemention you instantiate wherever, or even better is built-in to the language, it's easiest to reason with that implementation. Taken to the extreme, you get Enterprise FizzBuzz.
The effect that I'm complaining here is visible in "weaker" languages, like Java. People are too afraid to use reflection to instrument the code for testing, so e.g. in a codebase I'm working on right now at $JOB I get to see classes in which you can't really tell what code is there for business purposes and what code was added so that the business code could be tested.
I agree with you that a code that's hard or impossible to test is a code smell. I argue that having to modify a good design to accomodate for more testing is also a code smell. I think the two heuristics narrow down the design space nicely, pointing one towards designs that are easy to test because of their natural boundaries, and not because of additional testing cruft being added.
> Personally, what annoys me the most about TDD I've seen in the wild are two things
It's like saying that one is annoyed by programming in general because he's seen too many horrible things done with it! Anything can be abused, TDD is not an exception.
> your tests become more complicated than the tested code
Well, then don't tdd that code on unit level. Keep some high-level(smoke, integration etc) tests that executes it , relax and write/design it without TDD the best way you can.
> Tests affecting the structure - this is IMO a strong code smell.
Yes, and this smell (by definition) shows you a flaw in the code design. TDD helped to identify this. Apparently)
Personally, what annoys me the most about TDD I've seen in the wild are two things: designing for tests instead of actual problems, and tests affecting the structure of "real" code.
Designing for tests - the standard TDD approach, first we write tests, then we write code to pass the tests. Quite often the consideration of the problem being solved disappears. It's a fine approach when your task is to write a small black box that takes some data in and transforms it into something else. But I've never seen a case where someone made it work for complex tasks. It always ends up the same - your tests become more complicated than the tested code. It happens with any non-trivial problem, because the thinking you have to do to write tests that make sense is the same as the thinking you need to do to solve the problem in the first place. So you're basically writing the program twice, only in a convoluted way, and without considerations for global design.
Tests affecting the structure - this is IMO a strong code smell. If you're modifying your design to accomodate for tests, by e.g. adding superfluous dependencies, hooks or injection points, you've screwed up. It only makes the code more complicated and less reliable.
The only tests I've found valuable so far (in terms of effect for effort spent) are regression tests - the ones you write to catch bugs in order to make sure they won't happen again. Everything else in TDD seems to be easily replaceable by proper iterative programming.
Maybe that's why TDD is popular in the languages without a sane REPL.