Last month, I had the pleasure of sitting on a panel on software
testing along with Tammer Saleh, Bryan
Liles, and Sandi Metz.
It was a lot of fun. They’re a great group of folks and we had plenty
of interesting discussion about testing.
One of the questions we talked about was, “Bryan says to test all the
fucking
time.
ALL the time? Really? What about when writing
spikes?”Tammer suggested that you write your spike without tests and comment out all the code
when you’re done. You then start writing tests and uncomment only the
code that you need to make it pass, refactoring along the way. I love
this strategy when dealing with existing, untested production
code, but when Obie asked
if I had any thoughts, my response was, “Um, I basically hate that
idea.”
TDD and spikes are design tools. Test-driving your code is effective
with “design in the small,” helping you build objects that are
cohesive, loosely-coupled, and communicate via intention-revealing
interfaces. In
order to do this effectively though, you typically have to have some
idea of what you’re going to build. Personally, I find TDD to be an
excellent exploratory/discovery tool, but even I have a very tough
time being effective when I work with objects at a low-level if I
don’t have a higher-level picture in my mind. When that picture is
fuzzy or blank, spikes become invaluable. I can just hack my way to a
prototype. The code quality doesn’t really matter, because my main
goal is answering the question “how the hell am I going to do this?”
When I’m through spiking, I throw it away. Or at least commit it to a
temporary branch so I can reference it if I need to. I want to be
free from that code so I can combine the insight I’ve received from
the spike with the usual design benefits of TDD. If I leave that code
in place, it’s too easy for me to think that it’s good enough if I
just get it under test now. That’s not how you get the max benefit
from TDD.
BUT
Commenting out the code and then writing tests does give you a degree
of freedom that I hadn’t thought of during that panel. Probably my
favorite thing about TDD is that it lets me mentally separate the What
from the How. Figuring out which tests to write - specifying the
What - is frequently more difficult than making them pass. Tammer’s
approach gives you a great opportunity to write the tests you wish
could have written before…without the extra work of having to make
them pass. You can get moving pretty quickly, writing a test and
uncommenting a couple lines, rinse, repeat, with only occasional
slowdowns as you nudge the implementation a bit.
Another thing that didn’t dawn on me until recently is that the good
TDDers I’ve met are all good hackers as well. I suspect it’s because
good hackers quickly learn that the easiest way to make sure your code
still works is to write automated tests, and the easiest way to write
automated tests is to write them before the code. What this means is
that when Tammer writes a spike, the end result is going to be pretty
good code that gets the job done. From there, he writes the tests to
give him a safety net which he can use to refactor the code to make it
even better.
Test-after as a means of learning how to test
Writing code and testing it afterwards isn’t just for spikes though.
I think it can also be a useful strategy for learning how to write
tests. If you’ve been coding Ruby for the last two years but don’t
know RSpec (or test::unit, or shoulda, etc..), you’re going to have to
go through the learning curve, plain and simple. This means learning
new syntax, libraries and idioms. On the RSpec list, I occasionally
see questions like, “I wrote this test but it’s failing, but I know
the code works fine, why is RSpec stupid?” The problem comes from
thinking that the author wrote the test correctly in the first
place…clearly that’s not the case. By writing the code first and
then the tests, you get the effect of TDD applied to learning a
testing framework.
Now I want to make it absolutely clear that I don’t advocate
test-after as your main strategy. I do think that it can be
combined with spikes to write well-designed production code. And I
think it can be useful for learning how to test as well. The fact
is that TDD is hard to do well, and as with any difficult technique
requires practice. You’re not going to get there overnight, and it’s
perfectly fine to fudge the process if that’s what it takes to
develop your skills. Just be sure to practice real TDD too, so you
can eventually be test-driving by default.