TDD gurus more and more tell us that TDD is not about tests, it is about design. So I know some developers who create really great design without TDD. Should they practice TDD then?
TDD doesn't only help me come to the best final design, it helps me get there in fewer attempts.
I used to have two or three stabs at a problem before deciding which design I thought was best. Now, that exact same time is spent writing tests and focussing my mind on the correct design. And, as a bonus, it leaves me with a suite of repeatable tests. Win!
That said, there are inevitably going to be people for whom TDD offers nothing. As long as they still have automated, repeatable tests at the end of it, that's fine.
What this particular TDD guru is really trying to say isn't that TDD is a design process (although a number of people have unfortunately interpreted it that way). The message here is that using a process such as TDD, if you do it right, will tend to improve your overall design.
The fundamental concept is one that's much older than TDD; designing for testability. If you rigorously adhere to the SOLID principles, especially the Single Responsibility Principle, you'll find code very easy to test. On the other hand, if you tend to be a bit sloppier in your design, you'll probably find the process of writing unit tests to force you to do this more often, to avoid the frustration of (a) figuring out what needs to be tested, and (b) spending more time setting up dependencies than writing the actual tests.
You don't have to follow TDD to get the benefits of this, but it does help to write the tests early - preferably very soon after you implement a class, if not before or during. If you wait too long, you run the risk of the "dirty hybrid" tests the author is talking about, which don't have a lot of value because they're brittle and tend to break during harmless refactoring - not to mention making larger-scale redesigns an excruciatingly difficult process.
You can't know if you're truly designing for testability if you don't write tests, and haphazard "feature tests" with only 15% code coverage don't count.
Of course you can create good designs without ever writing a single test - but are you sure they're great designs? How do you know, if you they aren't clearly specified by tests? Testing uncovers a lot of issues, and while a QA process may find visible bugs, they won't uncover poor design decisions.
Simplistic answer coming from someone striving to learn TDD: You don't need it, but the major benefit it gives you is, quite simply, confidence: Confidence that your application works. Confidence that the use cases are satisfied. Confidence that your logic is sound for the "Foobar" module. Confidence that your code is structured properly to be maintainable and extensible six months down the road when the CEO wants to add in some new crazy feature he read about. Confidence that, when your application grows, the foundations have been laid for the architecture to not fall apart or require gobs of messy hacks to jury-rig new features.
I realize the above sounded a bit evangelical but that's how I see the benefit of TDD. Even if you can create good, solid, well-architected designs using TDD forces your hand into doing things right, and then proves that things are done right, and more importantly provides a baseline for keeping things right. From the very little I've dabbled in TDD, using it forced me to make the code clean and follow proper software engineering concepts, where otherwise I would do the "quickest thing possible" which often resulted in messy "hack" code.
I can talk only from my experience. For me TDD brought several things I didn't have before in my development style habits toolbox. Although, it is worth to say again that TDD is not solution to everything. I always try to separate exploration and production ready implementation. TDD in exploration phase is absolutely not needed and even is slowing down. On the other side for production ready code it brings several benefits which on short and long run are very valuable for the mental health of developers and karma of the project.
- TDD makes me think before I implement, which is usually very good practice which prevents lot of shoot and forget solutions
- It makes me think in small portions of problem, forcing me to break apart solution to small pieces that fit together.
- It makes me write very decoupled code because whenever I have to stub/mock/fake something which doesn't fit into the problem I naturally throw one "WTF, why should I do this if I don't have to be coupled with it". And it makes me realize connections between things better.
- It gives me set of easily executable checks for my code, so I don't have to go through painful "var_dump", "p", "pp", "echo" style of debugging of code. It just reports what is wrong, when is wrong. And if I don't have a check for it yet, it is just matter of adding simple test to check it over and over again.
- It makes me certain that my code works if all tests pass before we deploy. Then instead of eating my nails I go eating a cake and drinking coffe and enjoying my afternoons.
- High level tests are very nice in the cases you have to refactor something. If my module has to provide some functionality to outside world and I have developed functional/integration/cucumber tests to prove it's working correctly I will be very brave to refactor the hell out of that code. Several times I was faced with the code that didn't have tests and need to refactor it. In that case there were two ways to go in practice. 1) drop the code 2) skip the changes and leave it as it is.
There is one thing that TDD doesn't fix. If you don't know how to build the thing you are building, then TDD will not produce solution for you. You need to have rough "design" or overview of the problem and solution. TDD will make you just implement it in more elegant and maintainable way with the code of higher quality.
At last, I prefer thinking in BDD terms which lean on TDD practices. BDD makes me to implement solution using vocabulary of the domain and to make software fit the problem better.
There may be many ways to achieve great design, and there are unquestionably many different interpretations of what constitutes "great" - or even "good". I suspect most TDDers would not agree with you about the definition - that if we were to look at the code you feel is great, we would consider it less than great. TDD drives us to some very specific qualities, and these qualities are rarely found in non-TDD code.
Testability, obviously, is one of those qualities, and the overarching one. Extremely small methods and classes are perhaps a sub-characteristic, and this leads to excellent naming. Maybe you know programmers who achieve these qualities without doing TDD, but I don't.