Teaching BDD by pair-programming
December 17th, 2007
Recently (last few months) I've had the opportunity to show two developers some BDD techniques. The first was in Ruby, using RSpec (new URL!), the second was in C# using MbUnit and Rhino Mocks. Here are my observations about the process. This is quite a long post, and strays a bit into the technical details that can become obstacles to teaching BDD, but I decided to include as much as possible to emphasise just how tough I found it.
Programming BDD is, to me, all-or-nothing. There's no grey areas about what should be tested and what shouldn't, except in contentious cases where the framework is providing behaviour to your objects (such as ActiveRecord validations, which are a frequent discussion point on rspec-users). In general though, when you interface with other code, rather than include it, the red-green-blue / specify-implement-refactor cycle is pretty well defined - you test everthing, because you don't implement something that isn't specified. This unforgiving situation is the source of my first point...
Teaching BDD by pair-programming is hard
And it's hard on both of you. It's not hard to see why, as it's the sum of many different difficulties:
- the relentless pace of pair-programming is draining under any circumstances
- BDD is as much of a mental shift from procedural programming as functional programming, or using VisualBasic
- good tools won't help you learn the principles - BDD involves applying abstract concepts, not running code generators - but bad tools will hinder you in applying them
- you will come across problems that neither of you can solve immediately, and that means you are forced to learn, do and teach all at the same time
The truth is, these difficulties don't ever go away, just the balance of teacher/student, or learning/doing changes over time. And it can take a long time for that to stabilise - it certainly took me up to a year to really get BDD. Depending on your organisation, the amount of time you might spend pair-programming for training purposes might only be on the order of a few weeks or months. This means the BDD lightbulb is unlikely to come one while you are teaching. If, as a developer new to the ideas, = you aren't sold on BDD before that lightbulb comes on, you're likely to reject it as a waste of time. The reason being...
Writing tests looks like a lot of wasted effort (but isn't)
A perfect example of this was in my second pair-programming stint. The goal was to write a .Net web service client to make online finance applications. The tools we had were MbUnit and Rhino Mocks.
We started with some really basic functional tests for fetching transaction IDs, and some unit tests to get the implementation working. This went well - the functional tests ran as soon as we pointed the finished unit-tested code at a live URL, so clearly we didn't miss anything along the way.
The next step was to actually make a finance application. Now unit testing the code to actually making an application is easy - no harder than the code that queries the existing transaction IDs. But to write a functional test we needed a way to query the transactions on the system, and that involves implementing a whole piece of functionality that does not solve the problem originally at hand.
Bang - motivation went through the floor. Not mine - and I'm sure most experienced BDDers would also just crack on, knowing that the payoff of the functional tests is far bigger than the investment in a calling a few extra web service methods. But the developer I was helping saw this is a pointless waste of time, something that contributes nothing to the essential functionality. And there's a reason for that - he's right! It does contribute nothing to the essential functionality. But, that's not the point, and the point is hard to see because...
Coding BDD does not demonstrate the purpose of BDD...
The purpose of BDD is (to me anyway, your opinion may vary) to produce useful, elegant code at a consistently high rate. When you are teaching BDD in a pair programming situation you are demonstrating how to write superfluous lines of code that test trivial functionality and a speed most assembly programmers could match. Hell, even most VB programmers. (Wow look! Two digs already - I'm setting a record!)
My answer was this: Say we had wasted 8 hours implementing tests and functionality to test our core code. That's 8 hours of paid office time that achieved nothing. Well, from experience I can say for certain, you will spend those 8 hours sometime. And if it's not during office hours, it will be at 10pm, when everyone else is at home, and the MD is fuming because no payments are being collected because someone added or "fixed" something, and you have no idea why it broke. Now, if your company pays overtime (or gives time in lieu) and you have no pressing obligations at home, you might find this acceptable, even desirable. But most people on a fixed salary like to know they are free to go home at a fixed time, and I can tell you from personal experience, it's a whole lot more satisfying if you know things aren't going to randomly explode just as you step through the door. (Well, they may physically explode, but that's beyond any current unit testing framework to solve.)
The purpose of BDD is long-term. It's investment. It's "Someone forgot to write a test for when all constructor parameters are negative" vs "Sorry, can't go the gym with you tonight, gotta fix another uncaught null pointer message box when I highlight this Schnumple and click Floobalise twice... when logged in as Marjorie from accounts.. on her machine.".
Well, when I say that coding BDD does not demonstrate the purpose of BDD, that's true except for one situation...
...until it produces something working with almost no effort
This was the case with one of the first .Net functional tests. We implemented all the unit tests showing the interactions between the objects, went back to the functional test, ran it, and it worked. This was a pretty trivial case - my experience is that usually things almost work first time. But in the long haul, that's good enough. If your functional tests are at a sensible level of granularity (ie you can implement them in a few hours or days), you get enough little morale boosts that you stay enthusiastic. Again, this is difficult to get in a training situation, where one or both of you are unfamiliar with some key concepts (in my case, I knew very little about .Net web services). The low velocity exaggerates the effect of setbacks, and makes the wins look modest or even trivial (which often they should be, you should just be getting through more, faster).
In order to compensate for this drop in velocity, you need the best development environment you can get your hands on. Having done some BDD work in .Net, I can now say how incredibly spoiled Ruby programmers are with RSpec (and Ruby of course, which is what lets RSpec be as good as it is). This is best summed up by my feeling that...
Good tools won't help; bad tools will definitely hurt
I don't mean this entirely literally of course. Good tools do help, because you couldn't work without them. But learning the tools is the easy bit; learning the programming principles is much harder. I usually liken it to a piano: pianos are easy to use, you press a key, it makes a noise. Yet for some reason, there's a notable lack of piano maestros floating around. The problem with pianos is there's so many different keys! And they come in two colours! The problems with BDD boil down to syntax and style.
Syntax is the more superficial of the two: it's "x should y" vs "assert y x", "it 'should do something'" vs "public void test_it_does_something() { }" and so on. Now to call this superficial isn't to undermine its importance in getting the benefit of the higher levels of behaviour-driven development, but it's quite obvious what's going on. In many cases, you could translate test-framed sentences to behaviour-framed sentences automatically. Indeed, in C# and MbUnit, I took to doing this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
namespace MyStackLibTest
{
[TestFixture]
class ANewStack
{
private Stack stack;
[SetUp]
public void _()
{
stack = new Stack();
}
[Test]
public void ShouldBeEmpty()
{
Assert.True(stack.Empty());
}
}
}
|
In order to coax MbUnit into outputing test runs like this:
ANewStack._.ShouldBeEmpty
You will have to forgive any syntax or MbUnit errors in the above, as I typed it from memory. But, somehow, it is possible to make the syntax almost pleasant to read.
The style is much tougher. One stylistic point I try my damned hardest to adhere to is that each behaviour example should contain one expectation (or in some cases, a small number of inseparable expectations, such as with iterations or ordered expectations). This is really an issue for the mocking framework, and RSpec mocks is a dream to work with: behaviour specs written in RSpec does exactly what you think they will. Not so, unfortunately, with Rhino Mocks. You should be familiar with Ruby specs that look like that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
describe FantabulasticComplicatifier, "created with a Fluffgaboozle" do before(:each) do @fluffgaboozle = mock("fluffgaboozle") @fluffgaboozle.stub!(:do_something) @fluffgaboozle.stub!(:do_something_else) @fantabulastic_complicatifier = FantabulasticComplicatifier.new(@fluffgaboozle) end it "should use the Fluffgaboozle to :do_something with the uppercased first arg when sent :complicatify" do @fluffgaboozle.should_receive(:do_something).with("MOO") @fantabulastic_complicatifier.complicatify("moo", "quack") end it "should use the Fluffgaboozle to :do_something_else with the reversed second arg when sent :complicatify" do @fluffgaboozle.should_receive(:do_something_else).with("kcauq") @fantabulastic_complicatifier.complicatify("moo", "quack") end end |
Pretty elementary stuff - in fact, it's downright essential (in my opinion, at least) to be able to break things down like this if you want a chance of writing code that covers all your edge cases, and explains why your code behaves like it does, instead of testing that a list of arbitrary criteria are matched. But good luck doing this with Rhino Mocks: once you've set up a stub (or SetupResult.For in Rhino Mocks terms), you can't override it. In fact, you even have to be careful when you stub things, as any mocks created in the constructors of an object need to be replayed back at a specific time so they are available in that object's methods. (Rhino Mocks has explicit record and replay states for each mock).
Now these may not sound like things that will kill a BDD session stone-dead, and they aren't. But they do undermine the point I made earlier that BDD is about principles, not tools, and having tools that don't work how you expect them can be as much of a mental blocker as writing assert or test where you really mean should. In some ways they are much worse, as the tendency of most developers is to work around framework limitations, which in this case means doing things wrong. And that means, that as a teacher, you are forced to either compromise the quality of the things you are showing, or spend a long time working around them and explaining in intricate detail why such hoop-jumping is necessary.
Due to my unforgiving nature, and perhaps also due to my stubborn refusal to be defeated by C#, I chose the latter: I will do it right if it kills me. Well, actually what I did each time I encountered a problem was write the elegant solution in Ruby, then the long-winded C# after equivalent, explaining why each of the (frustratingly frequent) workarounds were needed in order to achieve the same test quality. I don't think this was appreciated from the "haha, your tools suck" perspective, but it did at least get over the point that BDD should be syntactically easy.
I don't care if someone comes away with the impression that C# sucks (because it does, compared to anything except Java), but I want to avoid at all costs giving the impression that BDD is too hard, too impractical, or too fiddly.
So far, pretty much everything I've had to say might sound negative. I say might, because one thing I said is that "BDD is hard". Now if you're Japanese and own a Nintendo, this probably sounds like a good thing. Level 473 here I come! But if you're American and own a PlayStation, you probably thought "but there's cheat codes, right?" Note: I am playing off the fact that since I only have 17 subscribers right now, there's only a few people I can offend with any given post :o)
My final point, then is a bit more positive. It's possibly the most useful piece of advice I can give anyone when teaching BDD by pairing, and that is...
Always keep typing!
My first fatal mistake when teaching BDD was during the early stages of showing Ruby and RSpec. I was trying to solve problems in my head that ordinarily I would just play around with in code. Except this time I had someone sitting impatiently next to me, wondering when I would show them something that might be of some actual use. In a mission to avoid putting bad code on the screen, I also avoided putting good code on the screen; in fact, I avoided putting any code on the screen. And that is definitely not productive.
What I learned mercifully early on is that the best way to teach BDD is to pretend that the other person isn't there. It helps if you border on madness, as you want to talk to yourself the whole time you are programming to explain why you are doing everything. (If you are fairly sane, and are used to programming in a productive language like Ruby, Python, Smalltalk, or something, give C# a go. It will dislodge just enough of your frontal lobes to have the desired effect.) The real benefit of BDD is it lets you keep working, safe in the knowledge that the worst you can do is make the testing library tell you you broke everything. Usually, you just break something, and, occasionally, you make something work! This is the day-to-day practice of BDD, and it's essential to put this across in the pair programming sessions. It leads to the best possible achievement, namely...
At some point, your pair jumps in and starts coding, then you win!
This is the best possible situation. It's the big payoff you invested in, while you were jabbering on about mocks and object interactions and loose coupling and blah and blah and... The sooner the keyboard is snatched from your vice-like grip (we were using Apple keyboards - I'm very fond of them, you know), the better. From my experience of teaching with Ruby and C#, it happened a lot more with the Ruby guy. It's obviously not a fair test, as it was two different developers and two different projects, but I am pretty confident that if they had been switched round, the C# guy would have picked up BDD in Ruby just as fast, and the Ruby guy would have been just as frustrated with C#. The reason is simple: RSpec doesn't obscure your intentions.
When you have a clear spec and an obvious hole in the implementation, the only thing you can do is jump in and start coding. If you don't feel this urge, you aren't a programmer! And as is always the case, coding brings insight, and that leads to ideas for new tests and code. Your job when teaching BDD is to make that as accessible as possible, and to hide as much of the technicalities as possible.
Final words
A lot of this article may sound negative. That is mainly because doing BDD is hard, teaching BDD is harder, and learning BDD is harder still. A lot of negative feelings are bound to come out in pair-programming sessions where both of you have to invest so much to achieve what looks like relatively little. My main goal here was to highlight the obstacles I encountered. And in reality, many of those obstacles were my own inadequacies: lack of experience articulating BDD concepts under pressure, lack of familiarity with tools, lack of empathy for the programmers trying to learn so much so fast (I did it the long slow way, constant bashing my head against a wall until I was forced to ask for help on rspec-users.) But I like uncovering my own inadequacies: the ones I can see are the only things about myself I can improve, and as such, pair-programming gives plenty of opportunities for self-improvement.
I hope this article will have some positive benefits for people in a similar situation. If you have anything to share, I would love to hear your comments. I really believe it is the most important of all the Agile buzzwords, and the one that deserves the most thought and commitment.
Shameless plug: I would have enjoyed the opportunity to do more BDD training in this manner. As it happens, I have recently left my employer, so right now all the BDD I do is for myself. However, I am available for work on a contract/consultancy basis if BDD (and other agile techniques for encouraging high code quality) is something that may help your organisation. I'm available at mail AT ashleymoran DOT me DOT uk if you prefer to contact me directly.

Leave a Reply