I’ve been talking to and interviewing quite a few junior software engineers and Rails bootcamp graduates of late. Since testing is so central to our work here at Stitch Fix I usually ask them about it and TDD. Often people respond sheepishly that they don’t really write tests first but they know they should. One person told me that their bootcamp class read the “TDD is dead. Long Live Testing” post by DHH and took away the lesson that testing wasn’t required. I’m pretty much 100% certain that’s not the correct interpretation. Perhaps it points to a problem with TDD in Rails development culture: testing is a requirement and TDD is a choice, but the two are often conflated.
Testing is Required
Testing is not required in a formal sense, and there are clearly times when you don’t have to write automated code tests (prototyping, code you will only run once, code you write purely for fun) but at Stitch Fix at least you will not be able to deploy code to any production system unless you have written tests to cover it. In all the discussions around TDD and how it helps (or doesn’t help) you write better code, the real reasons for testing are often lost. Nothing I’m writing in this post is new but for anyone who is unsure, here’s why you need to write tests:
- Tests help you isolate and fix bugs
When a bug is found in your software, the first thing you should do is write a test that fails because the bug exists. This forces you to identify the nature of the bug and find out how to reproduce it. You can then work on fixing the bug and when the test passes you will know you have succeeded. Your new test now protects the software against the bug resurfacing - if the bug comes back the test will fail. See this Wikipedia article on regression testing for more information. In my experience it is this use of testing that best articulates to junior engineers why testing is essential, but it is not often encountered when learning to code, when the focus is more on prototyping than long lived projects. - Tests give you confidence when making changes
If you are working day-to-day on the same brand new app you can probably hold all the code context in your head and be confident you know how a change in class Astatine will affect controller Bromine. Outside of that particular case, I can think of no other time when you can make any even slightly substantial change to a code base and be aware of all the unintended consequences. As a professional software developer you are more likely to be working under one of these conditions:
- you are working in a mature code base that is entirely new to you
- you are working in a code base that you last worked in months ago
- you are working in a familiar code base, but other engineers have been working in other parts of it, and your knowledge of how their code works is incomplete
In all these instances you will feel much more confident about any change you make if there is a good suite of tests. Good tests allow you to work more quickly in an unfamiliar code base because you don’t have to try and learn every line, you can just make your changes and run the tests to see if you broke anything. And if you are concerned about technical debt and code quality, and you should be, then good tests also make it easy to refactor. - Tests communicate your intent
There will always be a gap between what you want your software to do and what it actually does. Your tests force you to think about what you want it to do. Tests also make you think about what the software does (the outcomes) before you think about how it does it, and the how should always follow the what. Most essentially, your tests tell anyone else who might happen to look at the code what you were hoping it would do. In any long-lived project you are going to have collaborators. If you don't write tests, then your collaborators will hopefully assume you had a good reason to write the code you wrote, but will waste lots of time - yours and theirs - working out what you were thinking. Of course, if you're anything like me then the person you work with the most - your future self - will assume you were being a complete moron, will rewrite your code and add in the missing tests, all the while cursing your idiot name.
Testing makes the collaborative work of writing software easier and less stressful. It is required because we want to enjoy working with our colleagues and our past selves.
TDD is a choice
It does not logically follow that because testing is required tests must be written first - it’s far more important that the tests exist. Consequently, the reasons for using choosing TDD are focussed on the individual engineer and are much more subjective.
A way of working and a way of thinking
Different authors use different techniques for writing novels: some like to plan the plot intricately before they write the first sentence while others like to start with a premise and some characters, see where things take them and then edit the results. Good and bad books have been written with both methods.
When I’m writing software I am a planner. I like to clearly define the problem I am solving and work backwards, thinking thoroughly through all the ramifications before I start coding. I’m not generally an exploratory coder, beginning with an idea and seeing where things take me, then refining and editing to get to the right solution. Both of these styles of working are equally valid and both can produce good or bad code. Another technique is take a bunch of benzedrine and write one single huge main
method over three sleepless weeks. 1
TDD is for me!
I love Test Driven Development. It conforms closely to the way I think about writing code. Colleagues of mine at Stitch Fix work differently and write their tests at the end. I am not being humble when I tell you that my code is most likely inferior to theirs. In fact, since we all try to be good collaborators and align on coding style and patterns, you probably can’t even tell them apart once they’re merged to master.
One claim made about TDD’s detractors is that it is slow, and if you just want to bash out a quick idea then writing tests first is a distraction. It takes a while to become proficient at writing good tests quickly but once you can, TDD can lead to quicker development. If you have the right tests in place then you can spew out any old code as fast as you like as long as it passes the tests (produces the right outcomes). If you then decide it’s a solution worth pursuing in earnest then you can refactor to your heart’s content, since you’ve already got the tests in place.
The Schmalkaldic Wars
I wanted to end this post with a preposterous religious wars analogy, but the truth is that I have never met an experienced software engineer who didn’t understand and appreciate the value of testing. Testing is hard but it’s also essential. Test driven development is even harder, particularly if it doesn’t suit you. So if you think that might be the case, then give it up before it stops you learning how to write good tests and be a good software citizen.