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); } } }
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); }); } |
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 |