The Joy of Behavior Driven Development (BDD), or, Re-writing Code in Asserts is Not Unit Testing

I am a bit late to the game here, but I wanted to mention that I am quite a fan of behavior-driven development. When I was first introduced to behavior-driven development via RSpec, I was deeply unimpressed: all the tutorials and example code I saw looked like verbose re-writes of standard unit tests. Worse, it injected weird magic onto testing which obscured what was actually going on from a developer standpoint, which made it harder to debug why a particular failure occurred.

Something clicked this morning, though. On Twitter, someone asked What’s the argument against testing private methods again?, and I responded with You should only test private methods insofar as they impact user API. Test contracts: don’t just rewrite your code in asserts. And I suddenly realized the point of BDD.

The problem that BDD solves is unit testing code like this:

def doFoo() {
  myFoo()
  fooListener.onFoo(this)
}

The traditional way to approach this is to create a unit test to test doFoo, and in there validate that myFoo() was called by asserting its side effects, and then mock up a fooListener that checks for onFoo to be called with this as an argument. In short, the traditional way is to rewrite this code in assert statements.

But that’s not the worst part. The worst part is that the resulting unit test is simply floating in the air, without context. If it fails, it is completely unclear whether or not that failure is expected and acceptable (cruft to be removed) or if it is a critical piece of functionality that can’t be touched. The “why” behind the code is completely lost with parrot-style unit testing.

Behavior driven development (hereafter BDD) solves that problem. I have been working with BDD via EasyB (from whom I am stealing the scenario below). The thing that is distinct about BDD is that instead of just having a hunk of code that throws an exception if something changes, BDD drives the developer to think in terms of context and contract.

A unit test for a stack class might look something like this:

void testNull() {
  shouldFail(RuntimeException) { stack.push(null) }
  assertTrue stack.empty
}

In this case, a failure is simply reported as an AssertionError and a line number. The developer then has to struggle to figure out what exactly was intended on being tested here. In this case, what the code is testing is only slightly obtuse — in even slightly more complex cases (like the doFoo method above), it can be downright opaque.

It’s much more obvious what you’re talking about when you do something like this:

scenario "null is pushed onto empty stack", {
  given "an empty stack",{
    stack = new Stack()
  }
 
  when "null is pushed", {
    pushnull = {
      stack.push(null)
    }
  }
 
  then "an exception should be thrown", {
    ensureThrows(RuntimeException){
      pushnull()
    }
  }
 
  and "then the stack should still be empty", {
    stack.empty.shouldBe true
  }
}

It’s certainly more verbose, but the resulting test provides an actual contract for the stack class: something that can be used as an actual API to code against. This has always been the unfulfilled promise of unit tests: they actually provide a way to understand a class from the outside. When you run a string of these scenarios, you end up with an output file that looks like this:

33 specifications (including 2 pending) executed successfully

  Story: empty stack

    scenario null is pushed onto empty stack
      given an empty stack
      when null is pushed
      then an exception should be thrown
      then the stack should still be empty

    scenario pop is called on empty stack
      given an empty stack
      when pop is called
      then an exception should be thrown
      then the stack should still be empty

  Story: single value stack

    scenario pop is called on stack with one value
      given an empty stack with one pushed value
      when pop is called
      then that object should be returned
      then the stack should be empty

    scenario stack with one value is not empty
      given an empty stack with one pushed value
      then the stack should not be empty

    scenario peek is called
      given a stack containing an item
      when peek is called
      then it should provide the value of the most recent pushed value
      then the stack should not be empty
      then calling pop should also return the peeked value which is \
        the same as the original pushed value
      then the stack should  be empty
      then an example pending [PENDING]

[...]

Someone new to the code — even one who doesn’t know the language the code is written in — can easily read through that write-up and get a very solid sense of the behavior of the class, including functionality that is on the docket but not yet implemented. That’s pretty awesome.

This entry was posted in Classic, Programming Language Punditry. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

8 Comments

  1. Posted November 10, 2008 at 4:25 PM | Permalink

    That looks really nice – I like it a lot, but there must be something less-than-perfect about it. :) What kind of overhead is there? Does it take longer to write unit tests that way? What problems have you run into?

  2. Posted November 10, 2008 at 5:39 PM | Permalink

    I haven’t noticed a any more “overhead” than with JUnit-based solutions. It doesn’t take me any longer to write unit tests, but that’s because I tended to leave REALLY LONG messages on my assert statements in order to make it really clear what a particular failure might mean.

    The biggest issue I’m finding is that I’d like to extend EasyB — provide some additional methods inside the closures — but I haven’t found an easy way to do that yet.

  3. ben
    Posted November 11, 2008 at 7:46 AM | Permalink

    God i might go back to writing java thats so verbose.

  4. Posted November 11, 2008 at 9:02 AM | Permalink

    @ben

    BDD’s adoption was really driven by Ruby and RSpec. So obviously that succinctness-minded community found something to love in it.

    Ultimately, while BDD code has more lines to it, there is a lot more semantic value to a BDD test case instead of a straight assert-based test case. I’ll consider that a win.

  5. Posted November 11, 2008 at 3:17 PM | Permalink

    @ben If you do it right, most test code will be very verbose, but who cares? The more verbose the test, the better. Your production code tends to be more succinct, clearer, and — oh by the way — correct. And that is the goal.

  6. James
    Posted November 12, 2008 at 9:24 AM | Permalink

    Looks very interesting. Do you have an example of what the output looks like when the tests fail? The pass cases look very readable, but I’d be interested to see how easy it is to figure out what’s broken when a test fails.

  7. Posted November 12, 2008 at 6:05 PM | Permalink

    @James

    There’s a big FAILED on the user output. For developers, there’s a nice page with drill-downs that tell you what failed and at which point. Including stack trace, if that kind of thing rocks your world.

  8. Posted November 21, 2008 at 12:32 AM | Permalink

    One thing I’d like to note, because I’m not sure I made it clear — it’s not just that there is a pretty print-out of the states. What’s important is that BDD is implying a structure to the testing that xUnit testing does not have, which directs the testing much more carefully and greatly improves clarity. In contrast to xUnit’s tendency to act in terms of mimicking implementation, BDD forces the developer to at least speak in terms of proving out functionality.

One Trackback

  1. By Enfranchised Mind » How Should I Burn My Free Time? on January 23, 2009 at 11:00 AM

    [...] how to extend EasyB — Although EasyB is the best BDD framework in the Java/Groovy sphere, it still has some substantial limitations that need to be [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">