Wednesday, October 3rd, 2007...8:59 am
The difference between TDD and BDD
Jump to Comments
“It’s all about the words”. “Focus on behaviour”. “Design your API as a client of your own API”. “It’s a clearer way to express the same thing, so it helps communication with stakeholders”. Those are all typical responses to the questions “Why is Behaviour Driven Development better than Test Driven Development? Why should I switch to BDD?”
I’ve given this some thought. All these reasons are correct, of course. BDD uses different wording, which is much clearer. BDD focuses on specifying behaviour (though the benefit of that is often not clarified, and some people will claim that all you’re doing is writing your code twice). Designing your own API as your own client means you will design it more easily and more quickly. Being clearer, BDD does help to communicate with other coders or stakeholders (although I’m not entirely sure that the average spec is really client-friendly enough to show unfiltered).
Some of those are not really benefits of BDD, however - rather, they are benefits of Designing. Many programmers (particularly in the less sophisticated parts of the agile movement) tend to focus on code and ignore the rest. BDD provides them with a neat, coder-friendly way of doing design and specification. Of course that helps! There’s a reason why the industry standard development processes include a design phase, after all - it’s not just to delay the project!
I think there’s another important reason why BDD is better, as a test suite. It helps make your test suite less brittle in the face of specification changes. Here’s the reasoning.
Update: Please have a look at this article from Pat Maddox. BDD is a fairly new field that’s still evolving, and Pat’s article is right on the money. The approach I outline in this article is what Pat would call “pure interaction testing” and, as he points out, it should not be a goal - only an available technique. Currently, I use pure interaction testing for Rails controller testing, and state-based testing for ActiveRecord models. There’s certainly a place for the ideas exposed in this article, but they shouldn’t be used blindly everywhere.
BDD focuses on specifying what will happen next. The basic sentence structure of a BDD spec is:
TDD, on the other hand, focuses on setting up a set of conditions and then looking at the output:
Put in other words, BDD specifies behaviours while TDD specifies outcomes. The difference is more than just language, though. It’s a paradigm shift that has some severe consequences.
The main one, in my view, is that behaviours can be layered, while outcomes cannot. This might sound a bit cryptic, so let me explain. Let’s use the analogy of a big machine. At one end, you put a variety of raw materials, and at the other, you get a widget. Outcome testing will examine the widget from all angles. It will ask “Does this widget perform the functions that I expect a widget to perform?”. This is basically black box testing - and that’s what most integration tests do (and, sadly, many unit tests, as it’s often hard to separate the two without extensive mocking, which requires deep knowledge of the internals of the module you’re testing).
Behaviour specification turns this upside down. Instead of trying to specify the outcome, and using mocking only where you absolutely need to in order to get the thing working, you start with the goal to specify precisely those internals that you tried to avoid in TDD. “The machine should be able to turn sand and gold flakes into gold flaked glass; to do this, it should heat up the sand into molten glass. Then it should insert the gold flakes.” This requires internal knowledge of the machine.
Hopefully this is clear so far. Now here’s the clincher, the real advantage of behaviour specification over outcome testing. In order to test an outcome, you need all the parts up to that outcome to have done their job. If I want to test that my widget has a gold-flaked glass pane in it, I need the rest of the widget-building to have succeeded and not produced a molten lump of goo. In other words, outcome testing is quite brittle. If one of the parts malfunctions, there will be many knock-on effects onto other tests. You might point out that this is why we have unit tests, but my point is, BDD enables you to design your unit tests properly. Let’s take a fairly simple method as an example.
foo.extrapolate!
bar.extrapolate!
baz = intrapolate(foo, bar)
return ultrapolate(baz)
end
end
With a Test-First approach, we might have written something like this as a test, before writing this code:
foo = test_foo
bar = test_bar
test_result = Mogrifier.new.transmogrify(foo, bar)
assert test_result.ultrapolated?
assert test_result.intrapolated?
assert test_result.contains(foo.relevant_bit)
assert test_result.contains(bar.relevant_bit)
end
How would you write this as a specification?
describe Mogrifier, “transmogrify“ do
before(:each) do
@mogrifier = Mogrifier.new
end
it “should extrapolate foo“ do
foo = “foo“
foo.should_receive(:extrapolate!).and_return(“extrapolated foo“)
@mogrifier.my_method(foo, “bar“)
end
it “should extrapolate bar“ do
bar = “bar“
bar.should_receive(:extrapolate!).and_return(“extrapolated bar“)
@mogrifier.my_method(“foo“, bar)
end
it “should intrapolate“ do
@mogrifier.should_receive(:intrapolate)
@mogrifier.my_method(“foo“, “bar“)
end
it “should ultrapolate“ do
@mogrifier.should_receive(:ultrapolate)
end
end
Why is the test more brittle? It’s brittle because if the specification changes and states that actuallly, the transmogrifier should also encrypt its results, your test will break, whereas not one of your specs will break. In a BDD world, you’d write:
it “should encrypt“ do
mock_baz = mock(“baz“)
mock_baz.should_receive(:encrypt)
@mogrifier.should_receive(:ultrapolate).and_return(mock_baz)
end
And then you’d add the encrypt call to the transmogrify method. With TDD, on the other hand, any of your tests that depend on the outcome of the transmogrifier will have to change. You might even be quite stuck at this point, in this specific example, if the encryption happens to be one-way - if that were the case, you won’t be able to test the outcome at all, since it will be opaque! Then you’d have to redesign your code so that transmogrify doesn’t actually encrypt stuff, but some other method does. What a pain!
Behaviour specifications enable you to ensure that your transmogrifier code keeps extrapolating and intrapolating as required - no matter what it does with its input later. If it needs to do other stuff too, that’s fine, you can specify it as well - but each specification will (or should) focus on a single behaviour and that way, as long as your spec passes, you know that the specification is being met, and that foo and bar are still being extrapolated (in this example).
As the behaviours multiply, and the layering increases, this becomes even more useful. Because of the focus of each specification, there is rarely any need to change more than a handful of specifications when the code changes (if the specs are well-written and cleanly split out into small and specific behaviours). For instance, if the Polator changes its processes (but retains its interface), the Transmogrifier test code will remain the same. BDD encourages low coupling of tests, which, to be fair, TDD doesn’t encourage. In TDD, it’s easier to write a test that uses real objects (at least at first). In BDD, you simply cannot specify behaviours without using mocks.
This is the major winning point of BDD for me, the reason why it feels so much more right. I think that the clarity, the speed, design, words, etc, are all nice bonuses to this main benefit. If you have any comments on this, any disagreements, etc, do feel free to post something below!
PS:
On the other hand, please note that I don’t suggest that we shouldn’t use tests at all. As mentioned here, specs do not validate whether your system actually hangs together and works on the whole. Tests are the ones to give you that.
1 Comment
October 19th, 2007 at 12:33 pm
[…] I’ve been struggling a bit with my specifications approach lately. Well, only until I found out about the new Story Runner functionality in rspec. […]
Leave a Reply