A new BDD framework in .Net : Aubergine

by Tom 3. November 2009 08:02

Update !! this has changed a lot

 

Check this post !!!

 

 

 

Introduction

During my exploration of BDD frameworks for .NET  I only had one final runner-up as a BDD framework : Machine.Specifications . This is a very nice framework, but in my quest for the holy grail on BDD it got me started on thinking about an even better BDD framework. In this article I will layout my initial ideas about a BDD framework named Aubergine.

 

Likes and dislikes about MSpec

MSpec is in my personal opinion one of the best BDD frameworks available for .Net, due to the very nice syntax.

An example :

First I'll show you the account class :


    public class Account
    {
        public Decimal Balance {get;set;}
        public void Transfer(decimal amount,Account ToAccount)
        {
            if (Balance < amount)
            {
                throw new ArgumentOutOfRangeException("There is not enough money available on the account");
            }
            Balance-=amount;
            ToAccount.Balance+=amount;
        }
    }

Next up I will show you the specs in Mspec :

using System;
 
namespace Machine.Specifications.Example
{
[Subject(typeof(Account), "Funds transfer")]
  public class when_transferring_between_two_accounts
    : with_from_account_and_to_account
  {
    Because of = () =>
      fromAccount.Transfer(1m, toAccount);
 
    It should_debit_the_from_account_by_the_amount_transferred = () =>
      fromAccount.Balance.ShouldEqual(0m);
 
    It should_credit_the_to_account_by_the_amount_transferred = () =>
      toAccount.Balance.ShouldEqual(2m);
  }
 
[Subject(typeof(Account), "Funds transfer"), Tags("failure")]
  public class when_transferring_an_amount_larger_than_the_balance_of_the_from_account
    : with_from_account_and_to_account
  {
    static Exception exception;
    Because of =()=>
      exception = Catch.Exception(()=>fromAccount.Transfer(2m, toAccount));
 
    It should_not_allow_the_transfer =()=>
      exception.ShouldBeOfType<Exception>();
  }
 
  public class failure {}
 
  public abstract class with_from_account_and_to_account
  {
    protected static Account fromAccount;
    protected static Account toAccount;
 
    Establish context =()=>
    {
      fromAccount = new Account {Balance = 1m};
      toAccount = new Account {Balance = 1m};
    };
  }
}

While this is a very keen and intelligent approach there are a few things that I am not very fond of :

  • The "Subject" attribute : is really code litter, and not really acceptable IMHO
  • The fact that we use classes and subclasses for each scenario; sometimes this becomes too much of a hassle; your code gets split up into several places, and this is not very transparent anymore...
  • The lack of Story support
  • The catching of expected exceptions should not need a try catch block but have some kind of other mechanism.
    Edit: My bad;there is a mechanism in MSpec... However, I'm not a real fan of it, since it is in the because part. IMHO this should be an "it", not a "because"
  • The fact that the context is intermixed with the class itself => this might be a subject of discussion, but it is certainly not my personal preference; I prefer a seperate context class per story and/or scenario
  • Some definitions, i.e. "because of", "Establish","It" etc... are not good enough in my opinion
  • The use of underscores; I am more of a PascalCasing fan
    Edit: After the remark of Aaron, I decided to implement both

Based on my ideas I created a new BDD Framework : NetSpec

 

The Aubergine approach

Based on my findings I started coding this morning, and this is the spec defined in NetSpec :


    class TransferFundsBetweenAccounts : Story
    {
        AsAn AccountUser;
        IWant ToTransferMoneyBetweenAccounts;
        SoThat ICanHaveRealUseForMyMoney;

        class Transfer1MBetweenTwoAccounts : Scenario&lt;Context&gt;
        {
            Given BothAccountsHave1M = () => new Context(1m,1m);
            When Transfering1MFromAToB = c => c.AccountA.Transfer(1m,c.AccountB);
            ItShould Have0mOnAccountA = c => c.AccountA.Balance.ShouldEqual(0m);
            ItShould Have2mOnAccountB = c => c.AccountB.Balance.ShouldEqual(2m);
        }

        class TransferTooMuch : Scenario&lt;Context&gt;
        {
            Given BothAccountsHave1M = () => new Context(1m, 1m);
            When Transfering2MFromAToB = c => c.AccountA.Transfer(2m,c.AccountB);
            ItShould Have1mOnAccountA = c => c.AccountA.Balance.ShouldEqual(1m);
            ItShould Have1mOnAccountB = c => c.AccountB.Balance.ShouldEqual(1m);
            ItShouldFailWithError AmountToLarge = e =>
                   e.ShouldBeOfType&lt;ArgumentOutOfRangeException&gt;();
        }

        public class Context
        {
            public Context(decimal amounta, decimal amountb)
            {
                AccountA.Balance = amounta;
                AccountB.Balance = amountb;
            }
            public Account AccountA = new Account();
            public Account AccountB = new Account();
        }
    }

 

Edit : After Aaron's remarks I replaced


ItShouldThrowException&lt;ArgumentOutOfRangeException&gt; AmountToHigh;

with


ItShouldFailWithError AmountToLarge = e => e.ShouldBeOfType&lt;ArgumentOutOfRangeException&gt;();

which is closer to the domain expert's language then the developer's.

Now, I do not know what You think, but in my opinion this is a lot more readable then the MSpec version.

While the code currently compiles, the testing itself is not implemented yet, but this should not be such a big hurdle. The automatic catching of the exception should only happen in the "When"-phase afaik, other exceptions should ALWAYS return a fail.

 

Conclusion

In my quest for the holy grail I have met a lot of hurdles but I do think I am getting closer to my target. I hope/think that a domain expert should be able to read the code I present in the specifications; In my opinion this should be a viable DSL-like human-readable text. While the current representation still has some constraints due to it's implementation language, most of the disturbing stuff has been left out.

Up next is the implementation of the testrunner, but I will have to take a look at my schedule when I will be able to complete this, since I sometimes have to do some work that pays the bills as well ;) ...

If you have any suggestions or remarks please do let me know in the comment section !!

Bookmark and Share

Tags: , , , , ,

CodeProject | Development | News | Tom's blog

Comments

11/3/2009 12:38:29 PM #

Aaron Jensen

Hi Tom,

First off, thanks for the kind words about MSpec. I think you've got some really interesting ideas here. I did want to mention a few things about MSpec though, first is the whole with_ base class thing. I'm not a fan at all, I think that the way many have been using it is not a good practice. I actually address it here: codebetter.com/.../...n-about-mspec-practices.aspx

Also, MSpec does provide a way for catching exceptions:

exception = Catch.Exception(()=>thingThatThrows())

Believe it or not, I actually had ItShouldThrow in MSpec when it was first being developed, here's the commit where I updated the sample to have this: github.com/.../68204ab4b0028b2fba90ec3652cb9a50f372fa8f

I removed it though for a few reasons. For one, it was magic, it wasn't clear exactly what was happening. This wasn't the biggest reason though. The biggest reason is that in my opinion, "It should throw" should never appear in behavior specifications (except in API documentation, remember to consider your audience). This isn't English, it's programmer speak. The observation may be that it "failed" or "reported an error". Forcing the user to use a vocabulary that may not be optimal I didn't like. So I cut it.

I do think the generic base class is rather interesting. I've never really had a problem with just using statics, but I can see how this could further clean things up. It would also allow parallelizing of tests and eliminate issues with dirty static state. Good idea. I'm already considering it as an addition to MSpec... gotta think it through more though.

On the box_car vs PascalCase thing, I can only really provide anecdotal evidence that shows time and time again that box car is easier to read for things longer than a few words. I hated box car too for a long time, but it really is easier to read for *most* people. Internal biases developed from years of staring at PascalCased code can certainly make one think it's harder to read, but... well, all I can say is to each his own Smile Also, it makes it near impossible to properly capitalize proper nouns in your reports when everything is PascalCase.

By the way, if you want to continue implementing NetSpec, I'd suggest looking at MSpec's code base. Forking it and modifying it to behave like this may be easier than writing one from scratch.

Aaron Jensen United States |

11/3/2009 5:49:23 PM #

Tom Janssens

Hi Aaron,

Thank you for your opinion and ideas.

Regarding the exception code : you are right.

I decided to go for the ultimate test : showing the code to a non-it person (my wife) and asking her what she could make of it.
She understood the whole thing, except for the exception part.
However, I do have the idea that I should provide some kind of easy-access interface to exceptions being thrown...

After asking what would make sense to her and keeping in mind your remark, I suggested ItShouldFail, but she was more of a fan of ItShouldNotSucceed, so I will probably implement both and leave the choice to the user. One is of course not obliged to use this interface if he/she is not a fan of it.

I am glad that you like my idea about the base classes : Story and Scenario<TContext> .
I spent A LOT of time looking for the best syntactic representation and tried tens if not hundreds different approaches.
I must definitely say that the syntax evolved to something completely different since I started development this morning... Nevertheless, I am very pleased with the way it looks right now.

On the box_car versus PascalCase thing : since this is not so hard to implement, I'll just do both, and have some heuristic determine the casing used => again each user can use it the way he/she likes it... that should not be so hard...

Finally on the forking part : I took a quick peek at your code, and suppose i would need to implement a new AssemblyExplorer as well as some kind of ISpecificationRunner ? Would that be sufficient ??? If so I can definitely use your code I think.. I never forked something from a remote repository before, but I suppose there will be a lot of info about this on the web..

I'll probably post some more about this once I have evolved this to a more usable state, but for now I'll first need to focus on some work that pays the bills (Unless I find a generous sponsor ;) )

Anyway, I'll keep you posted !

Tom

Tom Janssens Belgium |

11/4/2009 12:40:44 PM #

trackback

A new BDD framework in .Net : NetSpec => Aubergine

A new BDD framework in .Net : NetSpec => Aubergine

Core bvba |

Comments are closed

About me

Tom Janssens op LinkedIn

Tom Janssens op twitter

Core bvba RSS

My name is Tom Janssens and I am the owner of Core bvba, a software and consultancy company.
I am married to Liesbeth and have 2 sons named Quinten and Matisse.
ICT is both my job and passion.
Next to this my other hobby is actively playing music (mostly guitar).

More info about me and my company...

Recent Comments