Hacker News new | ask | show | jobs
by chris_j 2121 days ago
For me, TDD is useful for a number of reasons, not all of which are directly related to testing.

Firstly, it forces me to think about the "what" and not the "how". When I start writing a unit of code, I write a test before I start writing the code that will make that test pass. This forces me to think more rigorously about what I want the code to do. The unit of code that the tests are driving might be as small as a single class, in which case I'm forced to clarify precisely what the responsibilities and collaborators of that class are. Or the unit of code might be as big as a whole service, in which case I'm forced to specify precisely what that service does, hopefully in term that the customer would understand. I find that in both cases (and especially the latter case), the tests become an executable specification of precisely what my unit of code (class or service or whatever) is meant to do. If the tests are written clearly then it's possible to read the tests and understand that specification.

Secondly, when doing TDD, especially when the tests drive small units of code, it forces me to design the code better than I perhaps would have done otherwise. If I have a class with many responsibilities and dozens of collaborators then it's really hard to write unit tests that drive that class. But that same class is also going to be pretty difficult to understand and to maintain when another developer opens it up again in a few months. TDD forces (or strongly encourages) you to design clean, simple units of code with a small number of collaborators, which tend to be easier to work with as time goes on. I had heard of the SOLID principles and Kent Beck's four simple rules of design but it was TDD that hammered home the fact that they are necessary if you want to make it easy to write code that is easy to work wit and easy to change.

Thirdly, a crucial part TDD is refactoring. (The TDD cycle is to write a failing test, make that test pass as simply as possible and then, crucially, refactor the code so that you're happy with the way that it looks.) The way I used to write code, the quality and design of the code would tend to slowly deteriorate over time as I kept changing it and I wouldn't know how to prevent that. Nowadays, I refactor all the time and the refactorings that I do tend to be very small and very safe. If the quality of the code got a little bit worse in order to make a test pass then I gently nudge it back in the right direction with a few safe behaviour-preserving refactorings which, nowadays, my IDE mostly does for me.

Fourthly, for me, TDD guarantees 100% test coverage of the behaviours that I care about. For every behaviour that I want my code to have, I write a test before I even implement that behaviour. I've used code coverage tools in the past but meh, all they do is tell me which lines get executed when I run all the tests. TDD gives me an executable specification of the behaviours that are important to my customers, with complete coverage of those behaviours, and they helpfully tell me if that specification is ever violated.

In sum, I find it very difficult to explain why TDD is useful. It took me 18 months or so to get over the hump and learn to do TDD well enough that it was giving me really significant advantages over the way that I worked before. After that, it kind of clicked and I finally got why this stuff was so powerful. But I don't think you can explain it reductively. TDD is a set of practices that you follow that give you emergent properties such as more correct, more maintainable code with better test coverage. If you're familiar with Cynefin (https://en.wikipedia.org/wiki/Cynefin_framework), I'd put learning TDD in the Complex domain, where cause and effect are obvious but only in retrospect. And now that I've learned to do TDD effectively, there's no way that I'd go back to the way that I used to work before.