Why I write tests - for myself
Popularized by Kent Beck and the Extreme Programming (XP) principles, TDD has become synonymous with programming best practices and it is rare to meet a professional programmer nowadays that argues against tests. This is good! But many people still find it hard to state specifically how writing tests will help them concretely.
The characteristics people generally cite for TDD are typically:
While these are all true, they are altruistic or abstract objectives with consequences far in the future. Objectives like these can be easily compromised when under pressure. For me, there are much more concrete and selfish reasons why I write tests - which is to avoid more work.
The point of programming is essentially to get as many people to use your code as possible. Unfortunately, the more people engage with your code, the more work if creates for you. Once it is written down, there are 3 key moments when your code will be read by someone else: when code is written, when code changes and when someone else needs to use it. In each of these instances, you could potentially end up with a pile of work on your desk.
The first time someone else will read your code is during a code review before it is merged to master or deployed to production. While “test-first” programming is recommended, I find it hard to know what to test before any code is written beyond basic return types and spending a lot of time in this phase usually means creating mental models of your code so why not write it down anyway? So in practice, I find myself more often than not having two tabs open writing code/pseudo-code and tests in unison. Writing tests while writing code creates a sort of devil’s advocate dynamic during the development process where I can use the test to challenge a function with a strange input, unexpected call sequence or performance/variable constraint tests. This can cover a lot of questions that come up in code reviews like: “What if you do this? Why did you do that? Did you check this? How does this behave at scale?”
The second time someone will read your code (might be future you) is when something needs to change or functionality needs to be added. Let’s say someone else comes along and adds some new minor functionality to your beautiful code and unknowingly breaks some existing functionality/constraint you had imposed because there was no test to catch it. Good tests will defend your beautiful code from someone trying to meddle with it in the future (again this could be future you). Good tests will act like constraints on the code that anyone else coming along will need to work around.
Because of the very nature of tests calling and utilizing the code you have written, they should give any newcomer a blueprint of how to call, use and integrate your code. Good tests will serve almost like examples, highlighting constraints, expected behaviour and performance considerations/approximations made. In this way, instead of asking you, how do I use this class, you can say: “Read the tests, see how it is done there.”
Thinking of tests with these objectives in mind also helps figuring out the what to test conundrum.
So in summary, if you want to avoid lengthy code review cycles, fixing things other people broke and explaining to everyone how to interface with your code, write tests!
- Murat (Dec 2019)