I don't practice test-driven development — at least not in the strict sense. When I sit down to code, the least productive thing for me is to try and write tests for a problem that I don't confidently understand. Perhaps others find that writing tests gives them a better understanding of the problem, but I find it constrains my thinking. At this stage in problem solving I need room to think and play, not a restrictive process that kills my creative thinking.
Until recently, a typical coding session would often look something like this:
- Fire up a REPL and start hacking. When this gets too tedious move to step 2.
- Move what I've learned to a source file. Write a bit of code that runs the code I just wrote.
- Make a change, run the code, observe the effect. Tweak. Repeat indefinitely.
- Once the problem is solved, cleanup and write some tests.
This approach proved effective, but it also made me dislike the practice of testing because it didn't fit into my overall process. I liked the fact that I had tests, but it felt awkward. Testing was something I did at the very end when I was already sure I had solved the problem.
Then it hit me. I was testing, and I started testing much earlier than I thought.
At some point between steps 2 and 3 I stopped playing and started testing. It was here that I fully understood the problem. My focus shifted from exploring and playing to making a change and observing it's effect. This was a test, it just wasn't automated or reproducible! I had discovered a problem with my approach.
A Finely Tuned Process
As I mulled this over I realized I had overlooked an opportunity to make better use of my time. I had spent a considerable amount of time automating other aspects of my development process, and yet here was a significant inefficiency that I had failed to uncover.
This realization led me to a different perspective. I began to think of unit testing as automating an existing process. Now, instead of writing and rewriting a bit of code that tests my implementation one piece at a time (or worse, hitting a route in my web app using a browser), I could test anything at any point. Not only was it automated, it was reproducible. Unit tests began to emerge naturally from my development process instead of being an afterthought.
This was a light bulb moment.
You're Already Testing
Unless you blindly ship your code to production under the assumption there will be no bugs (lol), testing is something you do already. Determining when you make the switch from playing to testing is the key to testing efficiently.
I've begun to think of TDD in this way:
TDD asserts that a development process is most effective when testing is performed by automated, reproducible tests.
I believe this definition is consistent with the spirit of TDD as typically defined. And interestingly enough, I've found myself following a true TDD process (as in writing the test before the implementation) by default when I test at the appropriate times. When I find myself jumping ahead and solving some small aspect of the larger problem without writing tests, it's usually because I lack a full understanding of the problem. In these cases it's most productive for me to put the tests away, step back, and play a bit more.
One way or another, we all test the code we write (as well as code we don't write, but that's for another day). Learning how to test effectively is really where it counts. Perhaps the exact approach differs a bit from one developer to the next. I'd love to hear how you test. If you have something to share, shoot me an email or tweet me @bryanp.