RBehave with NUnit
18 June, 2007. It was a Monday.
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
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
<p>
So I needed a way to capture the story concept using NUnit. I figured why not use the class text fixture attribute.
</p>
<table class="" cellspacing="0" cellpadding="2" width="600" border="1">
<tr>
<td class="" valign="top" width="354">
NUnit Syntax<span style="font-weight: bold;color: #9966cc"></span>
</td>
<td class="" valign="top" width="597">
<pre><font size="1"> <span>public</span> <span>class</span> <span>Transfer_to_cash_account</span> : </font><font size="1"><span>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)
*/
<blockquote>
<p>
<font size="1">}</font><a href="http://11011.net/software/vspaste"></a>
</p>
</blockquote>
</td>
</tr>
<tr>
<td class="" valign="top" width="354">
RBehave Syntax
</td>
<td class="" valign="top" width="597">
Story <span style="color: #996600">“transfer to cash account”</span>,<br />%<span style="font-weight: bold;color: #006600">(</span>As a savings account holder<br /> I want to transfer money from my savings account<br /> So that I can get cash easily from an ATM<span style="font-weight: bold;color: #006600">)</span> <span style="font-weight: bold;color: #9966cc">do</span>
</td>
</tr>
</table>
<p>
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.
</p>
<table class="" cellspacing="0" cellpadding="2" width="600" border="1">
<tr>
<td class="" valign="top" width="141">
NUnit<br />Syntax
</td>
<td class="" valign="top" width="1079">
<pre><font size="1"> [<span>Test</span>]
<span>public</span> <span>void</span> savings_account_is_in_credit()
{
<span>Account</span> savings = <span>null</span>;
<span>Account</span> cash = <span>null</span>;
<font color="#ff8000">Given</font>(<span>"my savings account balance is"</span>, 100,
<span>delegate</span>(<span>int</span> accountBallance)
{
savings = <span>new</span> <span>Account</span>(accountBallance);
});
<font color="#ff8000">Given</font>(<span>"my cash account balance is"</span>, 10,
<span>delegate</span>(<span>int</span> accountBallance)
{
cash = <span>new</span> <span>Account</span>(accountBallance);
});
<font color="#ff8000">When</font>(<span>"I transfer"</span>, 20,
<span>delegate</span>(<span>int</span> transferAmount)
{
savings.TransferTo(cash, transferAmount);
});
<font color="#ff8000">Then</font>(<span>"my savings account balance should be"</span>, 100,
<span>delegate</span>(<span>int</span> expectedBallance)
{
<span>Assert</span>.AreEqual(expectedBallance, savings.Ballance);
});
}</font></pre>
<p>
<a href="http://11011.net/software/vspaste"></a></td> </tr>
<tr>
<td class="" valign="top" width="141">
RBehave Syntax
</td>
<td class="" valign="top" width="1079">
<font size="1"> Scenario <span style="color: #996600">“savings account is in credit”</span> <span style="font-weight: bold;color: #9966cc">do</span><br /> Given <span style="color: #996600">“my savings account balance is”</span>, <span style="color: #006666">100</span> <span style="font-weight: bold;color: #9966cc">do</span> |balance|<br /> @savings_account = Account.<span style="color: #9900cc">new</span><span style="font-weight: bold;color: #006600">(</span>balance<span style="font-weight: bold;color: #006600">)</span><br /> <span style="font-weight: bold;color: #9966cc">end</span><br /> Given <span style="color: #996600">“my cash account balance is”</span>, <span style="color: #006666">10</span> <span style="font-weight: bold;color: #9966cc">do</span> |balance|<br /> @cash_account = Account.<span style="color: #9900cc">new</span><span style="font-weight: bold;color: #006600">(</span>balance<span style="font-weight: bold;color: #006600">)</span><br /> <span style="font-weight: bold;color: #9966cc">end</span><br /> <span style="font-weight: bold;color: #9966cc">When</span> <span style="color: #996600">“I transfer”</span>, <span style="color: #006666">20</span> <span style="font-weight: bold;color: #9966cc">do</span> |amount|<br /> @savings_account.<span style="color: #9900cc">transfer_to</span><span style="font-weight: bold;color: #006600">(</span>@cash_account, amount<span style="font-weight: bold;color: #006600">)</span><br /> <span style="font-weight: bold;color: #9966cc">end</span><br /> <span style="font-weight: bold;color: #9966cc">Then</span> <span style="color: #996600">“my savings account balance should be”</span>, <span style="color: #006666">80</span> <span style="font-weight: bold;color: #9966cc">do</span> |expected_amount|<br /> @savings_account.<span style="color: #9900cc">balance</span>.<span style="color: #9900cc">should</span> == expected_amount<br /> <span style="font-weight: bold;color: #9966cc">end</span><br /> <span style="font-weight: bold;color: #9966cc">Then</span> <span style="color: #996600">“my cash account balance should be”</span>, <span style="color: #006666">30</span> <span style="font-weight: bold;color: #9966cc">do</span> |expected_amount|<br /> @cash_account.<span style="color: #9900cc">balance</span>.<span style="color: #9900cc">should</span> == expected_amount<br /> <span style="font-weight: bold;color: #9966cc">end</span><br /> <span style="font-weight: bold;color: #9966cc">end</span></font>
</td>
</tr></tbody> </table>
<p>
I have intentionally left the account assertion at 100 so it will generate the following stack trace.
</p>
<pre><font size="1">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
<p>
<font size="1">at AlamoCoders.BDD.Domain.Specs.NBehaveAbstractFixture.Then[T](String message, T actionValue, action`1 delegateAction) in </font><a href="NBehaveAbstractFixture.cs*59*1"><font size="1">NBehaveAbstractFixture.cs:line 59 </font></a><br /><font size="1">at AlamoCoders.BDD.Domain.Account_Specs.Transfer_to_cash_account.savings_account_is_in_credit() in </font><a href="RBehave.cs*40*1"><font size="1">RBehave.cs:line 40</font></a>
</p>
<pre><font size="1">NUnit.Framework.AssertionException: Expected: 100
But was: 80
<p>
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.
</p>
<pre><font size="1"><span>using</span> System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
namespace AlamoCoders.BDD.Domain.Specs
{
[TestFixture]
public class NBehaveAbstractFixture
{
private string messageHeap;
<span>protected</span> <span>delegate</span> <span>void</span> <span>action</span><T>(T value);
[<span>SetUp</span>]
<span>protected</span> <span>virtual</span> <span>void</span> SetUp()
{
messageHeap = <span>"rn-Behavior Heap-rn"</span>;
}
[<span>TearDown</span>]
<span>protected</span> <span>virtual</span> <span>void</span> TearDown()
{
messageHeap = <span>string</span>.Empty;
}
<span>protected</span> <span>void</span> When<T>(<span>string</span> message, T actionValue, <span>action</span><T> delegateAction)
{
</font><font size="1"><span>try
{
InvokeDelegateAction(“When”, actionValue, delegateAction, message);
}
catch (Exception e)
{
throw BehaviorException(“When”, actionValue, e, message);
}
}
<span>protected</span> <span>void</span> Given<T>(<span>string</span> message, T actionValue, <span>action</span><T> delegateAction)
{
</font><font size="1"><span>try
{
InvokeDelegateAction(“Given”, actionValue, delegateAction, message);
}
catch (Exception e)
{
throw BehaviorException(“Given”, actionValue, e, message);
}
}
<span>protected</span> <span>void</span> Then<T>(<span>string</span> message, T actionValue, <span>action</span><T> delegateAction)
{
</font><font size="1"><span>try
{
InvokeDelegateAction(“Then”, actionValue, delegateAction, message);
}
catch (Exception e)
{
throw BehaviorException(“Then”, actionValue, e, message);
}
}
<span>private</span> <span>void</span> InvokeDelegateAction<T>(<span>string</span> methodBehavior, T actionValue, <span>action</span><T> delegateAction, <span>string</span> message)
{
delegateAction(actionValue);
AddMessageToMessageHeap(actionValue, message, methodBehavior);
}
<span>private</span> <span>ArgumentException</span> BehaviorException<T>(<span>string</span> methodBehavior, T actionValue, <span>Exception</span> e, <span>string</span> message)
{
AddMessageToMessageHeap(actionValue, message, methodBehavior);
<span>return</span> <span>new</span> <span>ArgumentException</span>(messageHeap, e);
}
<span>private</span> <span>void</span> AddMessageToMessageHeap<T>(T actionValue, <span>string</span> message, <span>string</span> methodBehavior)
{
messageHeap += <span>string</span>.Format(<span>"{0} {1}: {2}rn"</span>, methodBehavior, message, actionValue);
}
}
}
<p>
<a href="http://11011.net/software/vspaste"><a href="http://11011.net/software/vspaste"></a></p>
<p>
As you can see this is very crude but it works! Let me know what you think.
</p>