Introducing NUnit.Behave or insert what ever other catchy name

OK I was bored yesterday and I decided to update what I had worked on the other day from Dan North’s post on rbehave.

Let look at my original attempt.

        [Test]
        public void 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)
             */

            Account savings = null;
            Account cash = null;

            Scenario("savings account is in credit");
            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 to cash account", 20,
                 delegate(int transferAmount)
                     {
                         savings.TransferTo(cash, transferAmount);
                     });

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

        }

This will work but the delegates can get really old especially is you want to have several scenarios.  So I decided to use basially make a composite key out of the behavior and the message.  I then created a Hashtable to store the key with the corresponding delegate.  What does this allow me to do.  Well now I can write additional scenarios with out having to worry about the initial plumbing.  Observe.

        [Test]
        public void 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)
             */

            Account savings = null;
            Account cash = null;

            Scenario("savings account is in credit");
            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 to cash account", 20,
                 delegate(int transferAmount)
                     {
                         savings.TransferTo(cash, transferAmount);
                     });

            Then("my savings account balance should be", 80,
                 delegate(int expectedBallance)
                     {
                         Assert.AreEqual(expectedBallance, savings.Ballance);
                     });
            Then("my cash account balance should be", 30,
                 delegate(int expectedBallance)
                     {
                         Assert.AreEqual(expectedBallance, cash.Ballance);
                     });
            
            Given("my savings account balance is", 400);
            Given("my cash account balance is", 100);
            When("I transfer to cash account", 100);
            Then("my savings account balance should be", 300);
            Then("my cash account balance should be", 200);

        }

Much nicer but then I didn’t like how it read so I created a template pattern that would allow the Given, When, Then methods to return a conjunction type that would allow me to write more readable scenarios. For instance:

            Given("my savings account balance is", 500)
                .And("my cash account balance is", 20)
                .When("I transfer to cash account", 30)
                .Then("my savings account balance should be", 470)
                .And("my cash account balance should be", 50);


            Scenario("savings account is overdrawn");
            Given("my savings account balance is", -20)
                .And("my cash account balance is", 10)
                .When("I transfer to cash account", 20)
                .Then("my savings account balance should be", -20)
                .And("my cash account balance should be", 10);

Yup that’s the ticket.  Now you can run the test from within NUnit and the test runner will produce the following output in the console.

—————————————————–

Scenario: savings account is in credit
Given my savings account balance is: 100
And my cash account balance is: 10
When I transfer to cash account: 20
Then my savings account balance should be: 80
And my cash account balance should be: 30

Scenario: savings account is in credit
Given my savings account balance is: 400
And my cash account balance is: 100
When I transfer to cash account: 100
Then my savings account balance should be: 300
And my cash account balance should be: 200

Scenario: savings account is in credit
Given my savings account balance is: 500
And my cash account balance is: 20
When I transfer to cash account: 30
Then my savings account balance should be: 470
And my cash account balance should be: 50

Scenario: savings account is overdrawn
Given my savings account balance is: -20
And my cash account balance is: 10
When I transfer to cash account: 20
Then my savings account balance should be: -20
And my cash account balance should be: 10
—————————————————–

 

As you can see the message heap evaluates for redundant actions (“Given” “Given”) and replaces the second instance with “And” making the output much more readable.

Here is the entire test fixture.

using NUnit.Behave;
using NUnit.Framework;

namespace BehavioralExample
{
    public class Account_Specs : BehavioralFixture
    {
        [Test]
        public void 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)
             */

            Account savings = null;
            Account cash = null;

            Scenario("savings account is in credit");
            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 to cash account", 20,
                 delegate(int transferAmount)
                     {
                         savings.TransferTo(cash, transferAmount);
                     });

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

            Given("my savings account balance is", 400);
            Given("my cash account balance is", 100);
            When("I transfer to cash account", 100);
            Then("my savings account balance should be", 300);
            Then("my cash account balance should be", 200);

            Given("my savings account balance is", 500)
                .And("my cash account balance is", 20)
                .When("I transfer to cash account", 30)
                .Then("my savings account balance should be", 470)
                .And("my cash account balance should be", 50);


            Scenario("savings account is overdrawn");
            Given("my savings account balance is", -20)
                .And("my cash account balance is", 10)
                .When("I transfer to cash account", 20)
                .Then("my savings account balance should be", -20)
                .And("my cash account balance should be", 10);
        }
    }
}

public class Account
{
    private int accountBallance;

    public Account(int accountBallance)
    {
        this.accountBallance = accountBallance;
    }

    public int Ballance
    {
        get { return accountBallance; }
        set { accountBallance = value; }
    }

    public void TransferTo(Account account, int amount)
    {
        if (accountBallance > 0)
        {
            account.Ballance = account.Ballance + amount;
            Ballance = Ballance - amount;
        }
    }
}

I am uploading the code to Google code and should have it ready shortly.  Just having trouble with Ankh and Tortoise. 

I am not to crazy about inheriting an abstract class as Scott Bellware mentioned but I just can’t seem to get around it since C# is not a dynamic language.  Let me know what you all think?  Am I on the right track?  Either way I am having fun coding!

You can download the code from the following link:

http://code.google.com/p/nunitbehave/source

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Joe Ocampo

My personal philosophy is simple: "Have a good strategy that sets the environment for success through the enablement of the whole. Be agile but with a mind towards pragmatism. Delegate to the best qualified individuals, but don’t be afraid to involve yourself in all parts of a job. Treat everyone with respect, humility, and with a genuine pursuit towards excellence." Respected business and technical leader with expertise in directing organization towards effective results driven outcomes. Proven ability to perform and communicate from both technical and business perspectives. Strong technical and business acumen developed through experience, education and training. Provides the ability to utilize technology, harness business intelligence and execute strategically by optimizing systems, tools and process. Passionate about building people, companies and software by containing cost, maximizing operational throughput and capitalize on revenue. Looks to leverage the strengths of individuals and grow the organization to their maximum potential by harnessing the power of their collective whole and deliver results. Co-Founder of LosTechies.com
This entry was posted in BDD (Behavior Driven Development). Bookmark the permalink. Follow any comments here with the RSS feed for this post.

5 Responses to Introducing NUnit.Behave or insert what ever other catchy name

  1. joeyDotNet says:

    Very cool syntax indeed. I just can’t get enough of fluent interfaces…

    I have NO experience in extending any of the xUnit frameworks, but for what it’s worth, I’ve heard that MbUnit is a bit more extensible, but again, I totally can’t back that up. Just maybe something to look into…

    I’m a huge fan of using MbUnit… it’s like NUnit on steriods… :)

  2. Joe Ocampo says:

    Joey

    I haven’t looked into MbUnit since I have been using NUnit for some time. Not that I am thrilled with NUnit but it get the job done.

    I don’t think that it would be too hard to extended what I have done for NUnit to work with any other testing framework it’s just a matter of wrapping my head around the TestFixture and assertion model. But doable none the less.

    I can’t agree more on fluent interfaces.

  3. Steven Solomon says:

    I was just wondering why you created your own action delegate rather that use the .Net framework Action delegate? I change the code to use this, and it all works fine.

  4. Joe Ocampo says:

    Steven,

    I plead ignorance. Thanks for the update, I will update the code.

  5. Garth says:

    Maybe this could be combined with NSpec (http://nspec.tigris.org/) so you can drop the [Test] attribute and Asserts?