August 2007
Monthly Archive
Monthly Archive
Every once in awhile, in this Age of Aquarius, when I talk with people about Behavior Driven Development (or BDD) they mention that BDD sounds a lot like TDD done right, man. By that, they mean that like TDD, BDD enables you to think in terms of behavior (or tests/validation in TDD speak) up front (or first). The beauty of thinking about these constructs first is that it, in essence, drives development in a much more fluid manner that permits refactoring and collaboration with copasetic domain experts. This style of coding also builds in confidence up front.
Because it’s my bag, I happen to agree with this notion (that BDD is TDD done right) but I find that BDD makes it a whole lot easier because it drops the word “test” in favor of “should”. And, as I’ve found, should rolls off the tongue quite naturally, man. Take the simple example of a queue data structure. Imagine someone has asked you to describe one– maybe you’d say something like this (note that I even left out the hip filler words in your favor, baby):
A queue is a first in first out data structure. It accepts items via an
enqueuemethod and whendequeueis called, the oldest item (the item who was enqueued before everything else) in the queue should be returned. Ifdequeueis called on an empty queue, an exception should be thrown; plus,enqueueshouldn’t acceptnullas a value.
Notice how the phrase “should” popped up here and there quite naturally. Via this description, you can easily start to see BDD in action. For example, given the description of the queue, you can start to define behavior from the sentences like “enqueue shouldn’t accept null as a value.” There are a few different BDD frameworks for you to choose from on practically every platform– one of the more innovative ones is RSpec for Ruby. This particular framework has a particularly hip DSL for defining behavior– watch:
I’ll start with an easy behavior to define– an exception should be generated if someone attempts to enqueue null (or nil in hip Ruby terms). RSpec may look strange at first, but once you’re used to it, it looks quite natural (and elegant). First, rather than declaring a class, you use the describe phrase. You can also use a special method named before (the :each means for each specification) that acts like a normal fixture in xUnit terms.
require 'queue'
describe Queue do
before(:each) do
@queue = Queue.new
end
end
Next, you can define behavior via the tripping it phrase.
it "should raise an error if nil is enqueued" do
lambda { @queue.enqueue(nil) }.should raise_error(RuntimeError)
end
Note the lambda phrase– it’s a pretty slick mechanism much like a closure. You can imagine that RSpec keeps on truckin’ and takes the phrase @queue.enqueue(nil) and executes it within a try/catch (or rescue claus) checking what kind of error was raised. Of course, running this behavior at this point won’t work– the Queue class doesn’t exist! All I have to do is implement the desired behavior– raise an error if nil is enqueued.
class Queue
def enqueue(item)
if item == nil then raise RuntimeError.new("can not enqueue nil")
else
end
end
end
Now I can run it:
aglover$ spec queue_spec.rb
.
Finished in 0.005292 seconds
1 example, 0 failures
The next behavior regards dequeue when nothing has been enqueued.
it "should raise an error if there isn't anything to dequeue and dequeue is called" do
lambda {@queue.dequeue}.should raise_error(RuntimeError)
end
Implementing this behavior in the Queue class is pretty easy, man:
def dequeue
raise RuntimeError.new("nothing to dequeue")
end
The next behavior gets to the real meat of a queue– that is, when you enqueue an item (and it is the only item in a queue) when you call dequeue, you should get that item back.
it "should return item enqueued" do
@queue.enqueue("test")
@queue.dequeue.should == "test"
end
By coding this behavior, it becomes evident that the Queue needs some backing collection to actually hold items– otherwise the meat of a queue isn’t there. Isn’t cool, by the way, that RSpec (in concert with Ruby’s MOP implementation) allows me to call should on any old object?
def initialize
@items = []
end
With an internal array (defined in the constructor of the Queue class) now holding the items of the Queue, I can now code the enqueue method to first check for nil– if the item isn’t nil then add it to the array.
def enqueue(item)
if item == nil then raise RuntimeError.new("can not enqueue nil")
else @items << item
end
end
If I run my hip behavior class, it’ll still fail as the behavior method enqueues and then dequeues an item; hence, I need to code dequeue, which I can code per the spec: “when dequeue is called, the oldest item (the item who was enqueued before everything else) in the queue should be returned.”
def dequeue
if @items.empty? then raise RuntimeError.new("nothing to dequeue")
else return @items.delete_at(0)
end
end
Running my behavior now yields a pass. I’d like to add a few more behavior methods just to verify things are kosher though.
it "should dequeue first item added" do
[1,2,3,4].each{ | val | @queue.enqueue(val)}
@queue.dequeue.should == 1
end
The behavior method above adds a few items and ensures that the first one to be dequeued is the oldest that was added. Now that that works, I want one more behavior that ensures that things are dequeued in reverse order– as you can see below, it isn’t too hard to do in Ruby, man!
it "should dequeue items in reverse order" do
values = [1,2,3,4]
values.each{ | val | @queue.enqueue(val)}
values.each{ | val | @queue.dequeue.should == val }
end
So is BDD TDD done right? In essence, yes, but hopefully you’ve seen that BDD’s should construct makes the effort of defining behaviors up front quite easy. In fact, because it’s my bag, I think BDD is easier to adopt than strict TDD dogma. Dig it, man?
7 comments Tuesday 28 Aug 2007 | Andy | Developer Testing, Ruby
The weekly bag is back, baby!
0 comments Friday 24 Aug 2007 | Andy | Weekly Bag
I had the pleasure of meeting Gerard Meszaros, the author of the ever so hip “XUnit Test Patterns” book (which is in the Martin Fowler signature series…The CI Book is also a member, man) recently at a dinner hosted by Stelligent (my copasetic employer) and he said what I think is the quote of the year (so take note, man).
Because it’s his bag, in a conversation with roughly 30 people (over a fine meal consisting of fillet mignon among other tasty victuals) regarding Agile processes and the discipline required to maintain such agility, Gerard used the analogy that it is unacceptable for a doctor to go into an operating room without having scrubbed in (i.e. washing his/her hands); consequently, it should be impermissible for a developer to write code without having written some tests (ideally unit tests) for said code.
In fact, Darryl Taft of eWeek (who was also in attendance) captured Gerard’s words succinctly in his article “Agile Brings Professionalism to Software Development“:
“Not scrubbing before you go into surgery is a no-no, and not writing unit tests is not acceptable.”
Check out Darryl’s article and the next time you let it all hang out and fail to write a test for the nasty mess of code, you had better hope if you ever are cut open that the cutter washed their hands in advance, man!
0 comments Thursday 23 Aug 2007 | Andy | Developer Testing