My journey to BDD using NBehave

In the beginning, there was some code:

   1: public class Calculator
   2: {
   3:     private int _accumulator;
   4:  
   5:     public int Result
   6:     {
   7:         get { return _accumulator; }
   8:     }
   9:  
  10:     public void Add(int n)
  11:     {
  12:         _accumulator += n;
  13:     }
  14:  
  15:     public void Subtract(int n)
  16:     {
  17:         _accumulator -= n;
  18:     }
  19: }

[Well obviously the code I had written was a bit more complicated than this … but this should do for the sake of illustration]

And I realised that code was legacy code, and so I wrote some unit tests:

   1: [TestClass]
   2: public class CalculatorTests
   3: {
   4:     [TestMethod]
   5:     public void TestTheCalculator()
   6:     {
   7:         var calc = new Calculator();
   8:         calc.Add(10);
   9:         Assert.AreEqual(10, calc.Result);
  10:         calc.Add(5);
  11:         Assert.AreEqual(15, calc.Result);
  12:         calc.Subtract(7);
  13:         Assert.AreEqual(8, calc.Result);
  14:     }
  15: }

And after writing quite a few tests in this style, I started to realise just how horrible they are.  Firstly, the name of the test tells me nothing at all; secondly, the various operations and assertions all depend on each other; and thirdly, I need to read and understand all the code to actually get my head around the intent of the test.  And so I started using a new, simpler style:

   1: [TestClass]
   2: public class CalculatorTests
   3: {
   4:     [TestMethod]
   5:     public void Add_AddsNumberToTheAccumulator_ResultIsCorrect
   6:     {
   7:         var calc = new Calculator();
   8:         calc.Add(10);
   9:         Assert.AreEqual(10, calc.Result);
  10:     }
  11:  
  12:     [TestMethod]
  13:     public void Subtract_SubtractsNumberFromTheAccumulator_ResultIsCorrect
  14:     {
  15:         var calc = new Calculator();
  16:         calc.Subtract(7);
  17:         Assert.AreEqual(-7, calc.Result);
  18:     }
  19: }

Better!  But what if the Add method has some nasty side effect that results in it breaking on the third call?  And what if there is some quirk in the Subtract method that means it will not operate on the number 13? (IOW what happens when the tests get more complex).  In the same style as above, the tests become:

   1: public class Calculator
   2: {
   3:     public Calculator() { }
   4:  
   5:     public Calculator(int start) 
   6:     {
   7:         _accumulator = start;
   8:     }
   9:  
  10:     // Other code omitted for clarity
  11: }
  12:  
  13: [TestClass]
  14: public void CalculatorTests
  15: {
  16:     [TestMethod]
  17:     public void Add_RepeatedAddition_CheckThatThreeTriesDoNotFail()
  18:     {
  19:         var calc = new Calculator();
  20:         calc.Add(10);
  21:         calc.Add(10);
  22:         calc.Add(10);
  23:         Assert.AreEqual(30, calc.Result);
  24:     }
  25:  
  26:     [TestMethod]
  27:     public void Subtract_ApplyToThirteen_ResultIsStillCorrect()
  28:     {
  29:         var calc = new Calculator(13);
  30:         calc.Subtract(5);
  31:         Assert.AreEqual(8, calc.Result);
  32:     }
  33: }

The test relating to addition looks a little … odd.  If I hadn’t written that test then I honestly wouldn’t know what the point of it was.  And the second test?  Well, you notice that we are passing in some state and assigning it to our private field.  It’s very common to do that with Dependency Injection, but this is state, not a dependency, and so we should question it closer.  It becomes an increasingly valid concern as the state gets more and more complex.  After some careful thought, I removed the new constructor and rewrote these tests as:

   1: public void Calculator
   2: {
   3:     public void Reset()
   4:     {
   5:         _accumulator = 0;
   6:     }
   7:  
   8:     // Other code omitted for clarity
   9: }
  10:  
  11: [TestClass]
  12: public void CalculatorTests
  13: {
  14:     [TestMethod]
  15:     public void Add_RepeatedAddition_CheckThatThreeTriesDoNotFail()
  16:     {
  17:         // ARRANGE
  18:         var calc = new Calculator();
  19:         calc.Add(10);
  20:         calc.Add(10);
  21:  
  22:         // ACT
  23:         calc.Add(10);
  24:  
  25:         // ASSERT
  26:         Assert.AreEqual(30, calc.Result);
  27:     }
  28:  
  29:     [TestMethod]
  30:     public void Subtract_ApplyToThirteen_ResultIsStillCorrect()
  31:     {
  32:         // ARRANGE
  33:         var calc = new Calculator();
  34:         calc.SetupToBeThirteen();
  35:  
  36:         // ACT
  37:         calc.Subtract(5);
  38:  
  39:         // ASSERT
  40:         Assert.AreEqual(8, calc.Result);
  41:     }
  42: }
  43:  
  44: public static CalculatorExtensions
  45: {
  46:     public static void SetupToBeThirteen(this Calculator calc)
  47:     {
  48:         calc.Reset();
  49:         calc.Add(13);
  50:     }
  51: }

That certainly feels better.  I no longer have a class that is willy-nilly accepting state from unknown others, I’ve added a method which feels like it should be there anyway, my setup code is neatly wrapped up into a reusable and readable extension method, and the intent of the addition test is much clearer.

Now I’m of the opinion that once you grok and apply the AAA (Arrange, Act, Assert) syntax, then BDD is largely just a translation of terms you already know.  But if it’s so simple, then why bother at all?  Well, I found that using NBehave I can rewrite the above tests like so:

   1: [TestClass]
   2: public void CalculatorTests : ScenarioDrivenSpecBase
   3: {
   4:     protected override Feature CreateFeature()
   5:     {
   6:         return new Feature("arithmetic calculation")
   7:             .AddStory()
   8:             .AsA("user")
   9:             .IWant("my calculator to perform basic arithmetic")
  10:             .SoThat("I don't need to try and do it in my head");
  11:     }
  12:  
  13:  
  14:     [TestMethod]
  15:     public void Add_RepeatedAddition_CheckThatThreeTriesDoNotFail()
  16:     {
  17:         Calculator calc = null;
  18:  
  19:         Feature.AddScenario()
  20:             .Given("a calculator",        () => calc = new Calculator())
  21:             .And("I have added 10",       () => calc.Add(10))
  22:             .And("I have added 10 again", () => calc.Add(10))
  23:             .When("I add another 10",     () => calc.Add(10))
  24:             .Then("the sum should be 30", () => Assert.AreEqual(30, calc.Result));
  25:     }
  26:  
  27:     [TestMethod]
  28:     public void Subtract_ApplyToThirteen_ResultIsStillCorrect()
  29:     {
  30:         Calculator calc = null;
  31:  
  32:         Feature.AddScenario()
  33:             .Given("a calculator",                () => calc = new Calculator())
  34:             .And("which has a total of 13",       () => calc.SetupToBeThirteen())
  35:             .When("I perform a subtraction",      () => calc.Subtract(5))
  36:             .Then("the result should be correct", () => Assert.AreEqual(8, calc.Result));
  37:     }
  38: }
  39:  

And to me this represents a big step forward, even though it’s pretty easy to recast the previous tests in this style.  We are now linking tests directly to scenarios, and scenarios directly to user stories.  It becomes immensely clear why this test is present and what it’s hoping to achieve.  And on top of that, the tests become immensely readable.

[I would actually rename these tests before I was happy … I prefer the “ShouldDoSomething” style of test name … and there are also much cleaner ways of coding the assertions]

You may ask if this level of attention to detail is appropriate in tests around a simple calculator class.  The craftsman in me insists that if it’s worth doing, then it’s worth doing properly.  But that aside, my current project (4,000 tests and counting) has found that if tests are not scrupulously well written then they become a source of friction.  Applying this style of BDD reduced the friction and took away the drag factor.

Interestingly there are actually other ways of casting these tests with NBehave, and I will be posting about those soon, but this concludes the story of my personal journey into BDD.

PS. This NBehave syntax is available in the trunk and will be included in version 0.5.  If you can’t wait then you can download the assemblies from the build server.

April 7 2010
blog comments powered by Disqus