One of the most common questions asked by Rails developers new to testing is: how do I make testing a habitual part of my development work?
It’s one thing to know how to write tests. It’s another thing to actually write tests consistently as a normal part of your work.
In order to share with you how to make testing a habitual part of your development work, I conducted a poll among some of my peers in the Rails world to see what keeps them in the habit of writing tests consistently. I also examined my own motivations.
When I drew the commonalities among the answers, what I came up with was a trifecta not unlike Larry Wall’s three virtues of a great programmer. The trifecta is laziness, fear, and pride. Let’s examine each “virtue” individually.
It might sound funny to name laziness as the first motivation for writing tests habitually. After all, tests seem like extra work. Writing tests consistently seems like something that would require discipline. But for me and many of the people who responded to my poll, it’s quite the opposite.
The alternative to automated tests
The alternative to writing tests isn’t just doing nothing. The alternative to writing tests is to perform manual testing, to let your users test your application for you in production, or most likely, a combination of the two. The alternative to writing tests is to suffer great pain and toil.
The laziness factor also extends beyond QA. I personally find that the process of writing features is often easier and more pleasant when I’m writing with the assistance of tests than when I’m not.
Mental energy is a finite, precious resource that (for me at least) starts full in the morning and depletes throughout the day. When I’m working I don’t ever want to use more than the minimum amount of mental exertion necessary to complete a task.
If I write a feature without using tests, I’m often juggling the “deciding what to do” work and the “actually doing it” work at the same time, which has a cognitive cost more than twice as much as performing those two jobs separately in serial. When I build a feature with the aid of tests, the tests allow me to separate the “deciding what to do” work from the “actually doing it” work.
It works like this. First I capture what to do in the form of a test. Then I follow my own instructions by getting the test to pass. Then I repeat. This is a much lighter cognitive burden than if I were to juggle these different mental jobs and allows me to be productive for longer because I don’t run out of mental energy as early in the day.
It’s more difficult, time-consuming and unpleasant to work with messy code than to work with clear and tidy code.
Being a lazy person, difficult, time-consuming and unpleasant work is exactly what I don’t want to do. I want to do work that’s pleasant, quick and easy.
Unfortunately it’s not possible to have clean, understandable code without having automated tests. This might sound like a hyperbolic claim but it’s not. I can prove it based on a chain of truths.
The first truth is that it’s impossible to write a piece of code cleanly on the first try. Some amount of refactoring, typically a lot of refactoring, is necessary in order to get the code into a reasonably good state. This is true on a feature-by-feature basis but it’s especially true on the scale of a whole project codebase.
The second truth is that it’s impossible to do non-trivial refactorings without having automated tests. The feedback cycle is just too long when all the testing is done manually. Either that or the risk of refactoring without testing afterward is just too large to be justified.
So, if it’s impossible to have good code without refactoring, and it’s impossible to do refactoring without tests, then it’s impossible to have good code without tests.
My extreme personal laziness demands that I only write neat and understandable code. Therefore, I have to write tests in order to satisfy my laziness.
Fear is another powerful impetus for testing. If I don’t write tests for my features, it increases the risk that I release a bug to production. Bugs cause me shame and embarrassment. I don’t want to feel embarrassment or shame.
Bugs may also have negative business consequences to the company I work for. This could negatively affect the company’s ability or willingness to pay me as much as I want.
When laziness doesn’t drive me to write tests, fear often does.
Lastly there’s pride. (I find Larry Wall’s “hubris” a little too strong a word.)
Sometimes, when I’m tempted not to write a test for a feature, I imagine another developer stumbling across my work in the future and seeing that there are no tests. I imagine myself sheepishly admitting to that developer that I didn’t bother to write tests for that feature. Why didn’t I write tests? No good reason.
As the arrogant person that I am, this imaginary interaction brings me pain. I really don’t like the idea that somebody else would look at my work and make a (legitimate) negative judgment.
I also want my work to be exemplary. If we hire a junior developer where I work, I want to be able to point to my code and say “This is how we do it.” I don’t know how I would explain that my test coverage is poor but I want theirs to be good.
I’m not driven to write tests out of discipline. I also don’t consider testing to be “extra” effort but rather an effort-saver.
The main forces that drive me to write tests are laziness, fear and pride. Mostly laziness.