RBehave with NUnit


I just read Dan North’s article on Introducing RBehave.

In a jealous rage of not being able to utilize Ruby fully in .Net. (YET)  I decided to see if I could pull off what Dan had created in rbehave using NUnit.  Because I am a huge proponent of reuse, I didn’t want to extend my existing Test Coverage with a brand new framework.  After all BDD coupled with ReSharper and Visual Studio make for happy testing. 

Now before I have the entire TDD community up in arms with what I am about to propose this is merely a pattern for the porting of rbehave to NUnit without having to invest in a whole new framework.

I would like to reiterate Dan’s vision of what rbehave is intended to be.

“rbehave is a framework for defining and executing application requirements. Using the vocabulary of behaviour-driven development, you define a feature in terms of a Story with Scenarios that describe how the feature behaves. Using a minimum of syntax (a few “quotes” mostly), this becomes an executable and self-describing requirements document.”

Now here is Ruby code for using rbehave:

require ‘rubygems’
require ‘rbehave’
require ’spec’ # for “should” method</p>

require ‘account’ # the actual application code

Story “transfer to cash account”,
%(As a savings account holder
  I want to transfer money from my savings account
  So that I can get cash easily from an ATM) do

  Scenario “savings account is in credit” do
    Given “my savings account balance is”, 100 do |balance|
      @savings_account = Account.new(balance)
    end
    Given “my cash account balance is”, 10 do |balance|
      @cash_account = Account.new(balance)
    end
    When “I transfer”, 20 do |amount|
      @savings_account.transfer_to(@cash_account, amount)
    end
    Then “my savings account balance should be”, 80 do |expected_amount|
      @savings_account.balance.should == expected_amount
    end
    Then “my cash account balance should be”, 30 do |expected_amount|
      @cash_account.balance.should == expected_amount
    end
  end

  Scenario “savings account is overdrawn” do
    Given “my savings account balance is”, -20
    Given “my cash account balance is”, 10
    When “I transfer”, 20
    Then “my savings account balance should be”, -20
    Then “my cash account balance should be”, 10
  end
end</div> </div> </div> </div> </div>

 So I needed a way to capture the story concept using NUnit.  I figured why not use the class text fixture attribute.

NUnit Syntax
    public class Transfer_to_cash_account : NBehaveAbstractFixture
    {
        /*
         As a savings account holder
         I want to transfer money from my savings account
         So that I can get cash easily from an ATM)
         */

}

RBehave Syntax Story “transfer to cash account”,
%(As a savings account holder
  I want to transfer money from my savings account
  So that I can get cash easily from an ATM) do

As you can see the story “transfer to cash account” is the name of the public class Transfer_to_cash_account.  As I am sure you all noticed the [TestFixture] attribute is missing but you will also notice that the Transfer_to_cash_account class inherits from NBehaveAbstractFixture, more on this later.  Lets talk about the real meat here, the “Given” “When” “Then” constructs.

</tbody> </table>

I have intentionally left the account assertion at 100 so it will generate the following stack trace.

System.ArgumentException: 
-Behavior Heap-
Given my savings account balance is: 100
Given my cash account balance is: 10
When I transfer: 20
Then my savings account balance should be: 100

at AlamoCoders.BDD.Domain.Specs.NBehaveAbstractFixture.Then[T](String message, T actionValue, action`1 delegateAction) in NBehaveAbstractFixture.cs:line 59
at AlamoCoders.BDD.Domain.Account_Specs.Transfer_to_cash_account.savings_account_is_in_credit() in RBehave.cs:line 40

NUnit.Framework.AssertionException:   Expected: 100
  But was:  80

The magic is in the anonymous generic delegate.  Rather than tell you how this works let me show you.  Here is the code for the the NBehaveAbstractFixture.

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;

namespace AlamoCoders.BDD.Domain.Specs
{
    [TestFixture]
    public class NBehaveAbstractFixture
    {
        private string messageHeap;

        protected delegate void action<T>(T value);

        [SetUp]
        protected virtual void SetUp()
        {
            messageHeap = "rn-Behavior Heap-rn";
        }

        [TearDown]
        protected virtual void TearDown()
        {
            messageHeap = string.Empty;
        }

        protected void When<T>(string message, T actionValue, action<T> delegateAction)
        {
            try
            {
                InvokeDelegateAction("When", actionValue, delegateAction, message);
            }
            catch (Exception e)
            {
                throw BehaviorException("When", actionValue, e, message);
            }
        }

        protected void Given<T>(string message, T actionValue, action<T> delegateAction)
        {
            try
            {
                InvokeDelegateAction("Given", actionValue, delegateAction, message);
            }
            catch (Exception e)
            {
                throw BehaviorException("Given", actionValue, e, message);
            }
        }

        protected void Then<T>(string message, T actionValue, action<T> delegateAction)
        {
            try
            {
                InvokeDelegateAction("Then", actionValue, delegateAction, message);
            }
            catch (Exception e)
            {
                throw BehaviorException("Then", actionValue, e, message);
            }
        }

        private void InvokeDelegateAction<T>(string methodBehavior, T actionValue, action<T> delegateAction, string message)
        {
            delegateAction(actionValue);
            AddMessageToMessageHeap(actionValue, message, methodBehavior);
        }

        private ArgumentException BehaviorException<T>(string methodBehavior, T actionValue, Exception e, string message)
        {
            AddMessageToMessageHeap(actionValue, message, methodBehavior);
            return new ArgumentException(messageHeap, e);
        }

        private void AddMessageToMessageHeap<T>(T actionValue, string message, string methodBehavior)
        {
            messageHeap += string.Format("{0} {1}: {2}rn", methodBehavior, message, actionValue);
        }
    }
}

</p>

As you can see this is very crude but it works!  Let me know what you think.

NUnit
Syntax
        [Test]
        public void savings_account_is_in_credit()
        {
            Account savings = null;
            Account cash = null;

            Given("my savings account balance is", 100,
                delegate(int accountBallance)
                {
                    savings = new Account(accountBallance);
                });

            Given("my cash account balance is", 10,
                delegate(int accountBallance)
                {
                    cash = new Account(accountBallance);
                });

            When("I transfer", 20,
                delegate(int transferAmount)
                {
                    savings.TransferTo(cash, transferAmount);
                });

            Then("my savings account balance should be", 100,
                delegate(int expectedBallance)
                {
                    Assert.AreEqual(expectedBallance, savings.Ballance);
                });

        }

</td> </tr>

RBehave Syntax   Scenario “savings account is in credit” do
    Given “my savings account balance is”, 100 do |balance|
      @savings_account = Account.new(balance)
    end
    Given “my cash account balance is”, 10 do |balance|
      @cash_account = Account.new(balance)
    end
    When “I transfer”, 20 do |amount|
      @savings_account.transfer_to(@cash_account, amount)
    end
    Then “my savings account balance should be”, 80 do |expected_amount|
      @savings_account.balance.should == expected_amount
    end
    Then “my cash account balance should be”, 30 do |expected_amount|
      @cash_account.balance.should == expected_amount
    end
  end

Wife 1.0