Recently Code with Jason reader Kaemon L. wrote me with the following question:
“As a beginner, is it better to write tests before you code to make it pass? or is it better to code first, write tests for the code to pass, and then add more tests as you come across bugs? In my experience so far learning RSpec, I’ve found it easier to code first and then write tests afterwards. Only because when I would try to write tests first I wasn’t exactly sure what needed to be tested, or how I was planning to write the code.”
This is a great question. In addressing this question I find it useful to realize that when you’re learning testing you’re actually embarking on two parallel endeavors.
The two parallel endeavors of learning testing
The two parallel endeavors are:
1. Writing tests (outcome)
2. Learning how to write tests (education)
I think it’s useful to make the distinction between these two parts of the work. If you make the realization that achieving the desired outcome is only half your job, and the other half is learning, then it frees you up to do things “incorrectly” for the sake of moving forward.
With that out of the way, what’s actually better? Writing tests first or after?
What’s better, test-first or test-after?
I’m not sure that it makes sense to frame this question in terms of “better” or “worse”. When I think of test-driven development, I don’t think of it as “better” than test-after in all situations, I think of TDD as having certain advantages in certain scenarios.
What are the advantages of test-driven development?
TDD can separate the what from the how. If I write the test first, I can momentarily focus on what I want to accomplish and relieve my mind of the chore of thinking of how. Then, once I switch from writing the test to writing the implementation, I can free my mind of thinking about everything the feature needs to do and just focus on making the feature work.
TDD increases the chances that every single thing I’ve written is covered by a test. The “golden rule” of TDD (which I don’t always follow) is said to be “never write any new code without a failing test first”. If I follow that, I’m virtually guaranteed 100% test coverage.
TDD forces me to write easily-testable code. If I write the test first and the code after, I’m forced to write code that can be tested. There’s no other way. If I write the code first and try to test it afterward, I might find myself in a pickle. As a happy side benefit, code that’s easily testable happens to also usually be easy to understand and to work with.
TDD forces me to have a tight feedback loop. I write a test, I write some code. I write another test, I write some more code. When I write my tests after, I’m not forced to have such a fast feedback loop. There’s nothing stopping me from coding for hours before I stop myself and write a test.
If I choose to write my tests after my application code instead of before, I’m giving up the above benefits. But that doesn’t mean that test-after is automatically an inferior workflow in all situations.
Learning how to test (process) vs. producing tests (result)
Let’s go back to the two parallel endeavors listed above: writing tests and learning how to write tests. If I’m trying to write tests as I’m writing features and I just can’t figure out how to write the test first, then I have the following options:
1. Try to plow through and somehow write the test first anyway
2. Give up and don’t write any tests
3. Write the tests after
If #1 is too hard and I’m just hopelessly stuck, then #3 is a much better option than #2. Especially if I make a mental shift and switch from saying “I’m trying to write a test” to saying “I’m trying to learn how to write tests”. If all I’m trying to do is learn how to write tests, then anything goes. There’s literally nothing at all I could do wrong as part of my learning process, because the learning process is a separate job from producing results.
What about later?
Lastly, what if I get to the stage in my career where I’m fully comfortable with testing? Is TDD better than test-after? I would personally consider myself fully comfortable with testing at this stage in my career (although of course no one is ever “done” learning). I deliberately do not practice TDD 100% of the time. Sometimes I just find it too hard to write the test first. In these cases sometimes I’ll do a “spike” where I write some throwaway code just to get a feel for what the path forward might look like. Then I’ll discard my throwaway code afterward and start over now that I’m smarter. Other times I’ll just begin with the implementation and keep a list of notes like “write test for case X, write test for case Y”.
TDD: Advantageous, indispensable, but not universally “better”
To sum it all up: I’m not of the opinion that TDD is a universally superior workflow to non-TDD. I don’t think it’s important to hold oneself to TDD practices when learning testing. But once a person does reach a point of being comfortable with testing, TDD is an extremely valuable methodology to follow.