An Open Letter to Software Engineers
The Case for TDD
The instant gratification of writing code is hard to avoid. It almost always blinds you with the attraction of building something, most of the time, anything. The ease with which to conflate this appeal with the sense that both building and building well are one and the same is just as beguiling.
Imagine you spent the past couple days dreaming up the next big product to hit the tech industry. Perhaps you happened across some technical dogma that cautioned you against writing any code before having solidified a pristine idea of the end product. After hours of visualizing and diagramming the interaction amongst the various system components, you feel fulfilled in having done your ‘due diligence’.
Now that you have a sketch, the corollary is to start coding… right?
But… where exactly lies the intersection between the concept(s) and the implementation(s) thereof?
In the old days, my naïveté led me to believe that once a concept had been cross-examined to a sufficiently arbitrary degree, I was able to run free with code. After weeks, even months of toil, I found that this approach engendered much more pain than was necessary.
Before any objection to this point can be raised, let me concede that diagramming the idea of an application and its systems’ architecture, mocking up wireframes, identifying API routes, etc. are integral to the process at large. However, I found the implementation of these facets and how they manifest in your code are categorically distinct. The sketches will serve as a useful aide from high above, but are certainly not of the same utility to implementation as Unit, End-to-End, and Integration testing.
Test Driven Development (TDD) is a domain unto itself. The accompanying paradigm shift in writing failing tests first, then code second is potent beyond belief. In short, you will be forced to be sure of what any piece of your code will do from any number of angles; which means you have to be very clear on what each component of code will do before it is even written.
The tests and the tests alone are where your code is begotten and given its form. Without testing, you are stripped of two things paramount to excellent, functioning code:
- A guarantee that the components of your program are deterministic. That, given the same inputs, they will produce the expected outputs and behave in the way you envisioned.
- A context of why your code took on the shape that it did. Testing makes the code you write today easier to understand and reason about tomorrow (much less, weeks/months from now).
In effect, rigorous TDD reduces your cognitive load for future sprints. It demands scrupulous thought in the moments before you write any code. Unfortunately, testing doesn’t receive nor hold the appeal typically reserved for laconic, ingenious code.
To many (including my former self), testing is an afterthought. An oft-forgotten entity molded post hoc to fit the somewhat ambiguous code that you’re most certainly deluded into thinking works the way in which you expect.
As the mathematician Augustus De Morgan would attest, whatever can go wrong, will go wrong.
For the more experienced among us, the above point is salient. Your mind cannot account for the slew of ways through which your code can break in the way a well-thought out testing suite can. This is no more a slight against your skills as an engineer, as it is a revealing of the power and role TDD should play before you ship code.
If you can delay your impulse to code from the start, taking the time to write tests first will prove invaluable and immensely convenient.