Above all else, our software must be correct. Since correctness can only be measured at a single point in time, we need our software to be changeable in order to maintain correctness through time. In order to change one part of the system without breaking anything else we need to be able to understand our software and predict the impact our changes will have. The next obvious question is: how do we write software that is easy to understand?
The first problem is that you can’t tell how easy it is to understand your software because you always understand the software you’re currently writing. It’s not possible for you to imagine how difficult it would be for someone who has never seen your code before to understand it. Even a big tangly mess seems perfectly reasonable while you’re working on it because you understand it at that moment. If you’ve ever been whining about “who wrote this garbage piece of code” only to open git blame and discover it was you, six months ago, you’ve experienced how it’s possible to write code that even you will have a hard time understanding later on.
The feedback cycle is just too long if we have to wait until we forget about something and have to relearn it. By that point it will be too late and by the time we understand it again, we won’t be able to judge whether our changes or rewrites are easier to understand. We need immediate feedback, which we can get by writing isolated unit tests:
The benefit, the real benefit, of isolated tests for one function at a time is that those tests put tremendous pressure on our designs. Those tests are the ones that make it the most clear where our design problems are. Remember that the whole point of test-driven development is not to do testing.
One of the goals of test-driven development is to learn about the quality of our design. We use the theory that if our design has problems then the tests will be hard to write—the tests will be hard to understand. It will be difficult to write these small isolated tests to check one thing at a time. So we rely on these tiny isolated tests to give us feedback about our design and to identify the problems.J.B. Rainsberger, Integrated Tests Are A Scam
It turns out that everything that makes code difficult to understand also makes code difficult to write isolated tests for. That’s very useful because the habit of test-driven development can provide the immediate feedback we need to tell us how easy our code is to understand.
Writing tests can be used as a proxy for learning about code because writing tests is, in a way, teaching the computer about how your code works. If the tests are easy to write, then it was easy for the computer to learn to understand your code. If the tests were difficult or complicated to write then the computer had a difficult time understanding how your code works. If the computer has a difficult time understanding your code, then other people are going to have a hard time as well. And other people includes you-six-months-from-now.
Most of the people I work with eventually come to the realization that when you know how to design software that is free of interleaving that you don’t feel the need to write unit tests to make sure that it works. And that’s true. This is why I rank Testable as the fourth priority. Once you know how, it’s easy to write very high quality code without writing any tests for it. The reason I still write tests for my code is because of the short feedback cycles. JBrains is right—without the constant pressure you get from writing isolated tests for all your code it’s too easy to get just a little bit lazy. And maybe these three things are a little tangly and messy, but it doesn’t really seem that bad so I let myself get away with it. Test-Driven Design is a discipline. You have to do it all the time because you’ll still always think the code you just wrote is easy to understand no matter how good you get.
Scott Adams says that even after decades of writing Dilbert comics, he still can’t predict with a high certainty how much people will enjoy a new comic strip. He’s been surprised that some of his all time most popular strips have been ideas that he almost didn’t even bother drawing because he didn’t think they were funny. And plenty of his favorite strips were gigantic flops.
When you’re too close to something you’ll never be able to evaluate it from the perspective of an outsider. You have to approach your work with the discipline that you’re going to iterate with fast feedback cycles. The only way to get that fast feedback on your software is to write the isolated tests.