Is BDD the new TDD? Adventures with RSpec

February 5, 2008

At Evri, I have the privilege of working with people who make it their business to write software in the most productively lazy way possible, by that I mean they strenuously avoid making rounder wheels. So when I see one of them start to use a new technology, I can only conclude that the technology must be making their (coding) life easier.

The first time I heard about rspec was when Phil Hagelberg had a practice run of his RailsConf presentation ‘tightening the feedback loop’ at one of our brown bags. He mentioned rspec along with rcov and flog. While rcov and flog struck me as having immediate value, I wasn’t so convinced about rspec. After all, isnt that what TestUnit is for?

At the time I was head deep in some Java code and couldn’t quite get to trying out rspec. When I surfaced, I felt very complicated and was happy to dive back into Ruby. However I had still forgotten about rspec and was still doing ‘old school TDD’ until I noticed that Travis, a notoriously ‘lazy bastard’, had completely switched over to rspec.

So I tried it, still skeptical. The whole ‘BDD vs TDD’ thing still confuses me, it’s like two people arguing whether chartreuse is really yellow or green. The whole point is to specify your expectations first, right?

My skepticism quickly faded as I started to use rspec. The best thing I can say about rspec is that it makes writing tests first so much easier. I believe it’s due to the DSL. Using rspec let me focus on what I wanted my class to do in a way that felt much more natural than writing tests for specific failure conditions. Instead of saying

class FooTest < Test::Unit::TestCase

def test_valid_foo_returned()

class_under_test = classUnderTest.new

foo = class_under_test.method1

assert(foo != nil)

assert_equal(foo.class.to_s,”Foo”)

end

end

I would instead say

describe classUnderTest do

it ‘should return a valid object Foo from method1 ‘ do

class_under_test = classUnderTest.new

foo = class_under_test.method1

foo.should_not eql(nil)

foo.class.to_s.should_eql(“Foo”)

end

end

I think a lot of people would look at the two code snippets above and think ‘chartreuse’. I know that’s what I was thinking. So what is the big deal?

First: the DSL lets me express my expectations about how the class under test behaves, using should and should_not. What I found is that tests tend to write themselves, and then Red/Green testing enables me to write the smallest amount of code to get past each line. In the example above, the description ‘it should return a valid object foo from method1’ allows me to stay clear about what I’m expecting from method1.

Compare that to the standard Test::Unit approach. The test that I wrote above does the same thing as the rspec code, but it doesn’t reinforce the fact that I’m testing a specific class and expecting specific behaviors. It may validate those behaviors, but I still have to go through a layer of translation, figuring out what each assertion really means, in order to understand the test.

That extra layer of translation makes Test::Unit start to feel heavier and slower because I’m still translating what I want to test into a test method, instead of having a method help me outline desired behavior and expectations. The extra layer is more energy I have to expend to use and maintain the test — energy that I could be using to write code, energy that I will not want to expend when I’m under deadline pressure.
I’m still playing around with rspec — I’m a newbie, and am still getting used to the way other users avoid fixtures, how and when to use mocks, stubs, and which is which, but so far I think it has gone a long ways toward keeping my coding restricted to fulfilling expectations and nothing more.

So life is easier with little effort — this is something that makes me extremely happy. Again, I don’t know whether to call it BDD, or TDD, or Fred, but this approach is working for me and I don’t care to debate the nuances. That said, I will continue to educate myself about the nuances and hope that some kind of enlightenment occurs 🙂

I will continue to explore rspec and other tools that make coding/maintenance easier. Specifically, I’m curious about:

  1. whether a story is an analog of a Test::Unit::TestSuite
  2. how matchers work — when I need them, etc.
  3. when to use a mock — when do I know that a real object is too painful/expensive? I’m not sure it makes sense to mock the model layer b/c I get implicit model layer testing when I use it, and if the model layer changes, my tests will (appropriately) break.
Advertisements