Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#

Getting started with BDD (Behavior Driven Development) in .NET

Rate me:
Please Sign up or sign in to vote.
4.68/5 (11 votes)
9 Nov 2009CPOL8 min read 80K   554   28   13
This article is a lean and mean/quick intro on how to get started with BDD in .NET.

Geting started

Introduction : Getting started with BDD

This article will give you a quick introduction on how to get started with BDD (Behaviour Driven Development) in .NET. It also contains a direct hands-on example using one of the .NET BDD frameworks. There is a lot of extra info in this article, but of course, you are free to skip the parts that are not interesting to you.

Background (How to dig a tunnel)

As you might know (or not) from my other articles, I have a very direct/hands-on development style (a.k.a. joel-on-software duct-tape programmer).

Dgging a tunnel

The TDD toolset

In the beginning, I was really fond of the whole unit-testing/TDD buzz, but after using it for a while, it seemed too much of a hassle to me. Why would you need to test a certain percentage of your code (i.e., code coverage), and how could you make sure the tests were relevant at all?

I did, however, use it in really big companies, and although there were almost no bugs when the code was released, I could not be convinced of the TDD approach using code coverage and other tools for verification in the build process. To me, it looked like I was trying to dig a tunnel under a river using the wrong tool set: a lot of spoons and small cups: it got the job done (i.e., almost bug free software), but it did not feel natural at all, and it required a lot of work!!

Functional testing

I also tried some functional testing (Fitnesse), and while this was a lot better, it still felt a bit awkward. It felt like I was using tools that were not really customized for the job... Continuing on the metaphors: this was like using a digging machine that is being used for construction sites to dig tunnels: it is definitely better then the spoon/cup approach, but there should be better options available.

BDD

A few weeks ago, I decided to get up to date with the newest buzz in the dev world: BDD.

I started digging the general BDD articles (i.e., Dan North etc.), and then got into the whole BDD experience using Machine.Specifications. This is a very good BDD framework for .NET, and got me easily convinced of the possibilities and advantages of BDD.

[Spam mode] But, since there were a few things that I was not really convinced about, and I always have been a little bit hard-headed, I decided to create my own BDD framework. So, I started coding... and after a few versions, and taking some inspiration from ruby/cucumber (the holy grail of BDD dev tools), I released my very first alpha of a .NET BDD framework: Aubergine. [/Spam mode]

After asking around a bit in my current range of friends and (ex-)colleagues about their opinion on the framework, it seemed to me they either did not knew what it was about, or they had some kind of a vague idea what it was supposed to do...But they all said it had a very high "coolness" factor.

This got me thinking: "Hey, why don't I write an article for CodeProject on BDD in general, and taking your first steps?" So this is why I wrote the article...

What is BDD and why should I use it?

As always, when I study something, I first need to know what I am studying, so I checked the reference I always check first: Wikipedia.

Behavior Driven Development (or BDD) is an Agile software development technique that encourages collaboration between developers, QA, and non-technical or business participants in a software project. It was originally conceived in 2003 by Dan North[1] as a response to Test Driven Development, and has evolved over the last few years[2].

The focus of BDD is the language and interactions used in the process of software development. Behavior-driven developers use their native language in combination with the ubiquitous language of Domain Driven Design to describe the purpose and benefit of their code. This allows the developers to focus on why the code should be created, rather than the technical details, and minimizes translation between the technical language in which the code is written and the domain language spoken by the business, users, stakeholders, project management etc.

Wow !! That is a long definition !! So the developers talk the same language as the domain experts? Man, this sounds neat, no more wrong requirements etc... So what is this ubiquitous language exactly then? After looking a bit further, I noticed the true beauty of BDD; developers and domain experts do indeed talk the same language, so no more misunderstandings...

The domain experts define what they need in the program in a way that the developers can not misinterpret (or at least not as much as in most other approaches).

This is all very abstract to me, please get on with it !!!

OK, OK, I got a little carried away writing this article. We will just see a small BDD example by one of the gurus on BDD: Dan North.

Story: Account Holder withdraws cash 
-

As an Account Holder
I want to withdraw cash from an ATM
So that I can get money when the bank is closed
-
Scenario 1: Account has sufficient funds
Given the account balance is \$100 
  And the card is valid 
  And the machine contains enough money
 When the Account Holder requests \$20
 Then the ATM should dispense \$20 
  And the account balance should be \$80 
  And the card should be returned
-
Scenario 2: Account has insufficient funds
Given the account balance is \$10 
  And the card is valid 
  And the machine contains enough money
 When the Account Holder requests \$20
 Then the ATM should not dispense any money 
  And the ATM should say there are insufficient funds 
  And the account balance should be \$20 
  And the card should be returned
-
Scenario 3: Card has been disabled
Given the card is disabled
 When the Account Holder requests \$20
 Then the ATM should retain the card
  And the ATM should say the card has been retained

Man!! That looks nice, and I must admit misunderstandings are hard to find here (given you have some minor domain knowledge, of course).

Very nice, but what does this have to do with my code ?

Not a lot yet, but you will soon see where we are going. It goes without saying that I will use my own BDD framework for showing you how to do it (sorry Aaron Jensen ;) ).

For the example, we will create both the domain code and the BDD code in the same lib, but in real life, your BDD code should, of course, be separated.

  • Create a new project and add a folder lib to it.
  • Download the attached example code, take the Aubergine lib and runner from the example/lib folder, and put it in the lib folder of your own project.
  • Add a reference from the lib (Be.Corebvba.Aubergine) to your project.

Next, we will define our story in a class; add the following class (do not worry about the AccountContext class, this will be our next step):

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Be.Corebvba.Aubergine;

namespace Com.CodeProject.BDDExample
{
    class Account_Holder_withdraws_cash : Story<AccountContext>
    {
        As_an Account_Holder;
        I_want to_withdraw_cash_from_an_ATM;
        So_that I_can_get_money_when_the_bank_is_closed;
        class Account_has_sufficient_funds : Scenario
        {
            Given the_account_balance_is_100;
            Given the_card_is_valid;
            Given the_machine_contains_enough_money;
            When the_Account_Holder_requests_20;
            Then the_ATM_should_dispense_20;
            Then the_account_balance_should_be_80;
            Then the_card_should_be_returned;
        }
        class Account_has_insufficient_funds : Scenario
        {
            Given the_account_balance_is_10;
            Given the_card_is_valid;
            Given the_machine_contains_enough_money;
            When the_Account_Holder_requests_20;
            Then the_ATM_should_not_dispense_any_money;
            Then the_ATM_should_say_there_are_insufficient_funds;
            Then the_account_balance_should_be_10;
            Then the_card_should_be_returned;
        }
        class Card_has_been_disabled : Scenario
        {
            Given the_card_is_disabled;
            When the_Account_Holder_requests_20;
            Then the_ATM_should_retain_the_card;
            Then the_ATM_should_say_the_card_has_been_retained;
        }
    }
}

Now we need to be able to execute this story, so we write the DSL part we wrote the story for

Create a class called AccountContext, and start implementing your DSL; I simply copy the Given/Then/When aspects of a story, and then I implement them in a DSL.

Please note that the context class will not be compilable yet, due to the unimplemented domain classes at the moment (this will be the next step).

Also note that writing this DSL already makes you think on the design of your domain; during this phase, you will probably do have to rethink some of your initial class designs you had in mind, since they somehow do not seem to fit the tests. My class looked like this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Be.Corebvba.Aubergine;

namespace Com.CodeProject.BDDExample
{
    class AccountContext
    {
        Account account = new Account();
        ATM atm = new ATM();
        Card card = new Card();
        decimal AmountDispensed;
        public AccountContext()
        {
            atm.Card = card;
        }
        [DSL(@"the_account_balance_is_(?<amount>.+)")]
        void AccBalIsX(decimal amount)
        {
            account.Balance = amount;
        }
        [DSL(@"the_card_is_(?<state>.+)")]
        void CardStateIsx(CardState state)
        {
            card.State = state;
        }
        [DSL(@"the_machine_contains_(?<amount>.+)")]
        void AtmContainsX(decimal amount)
        {
            atm.AvailableCash = amount;
        }
        [DSL(@"the_Account_Holder_requests_(?<amount>.+)")]
        void Requestsx(decimal amount)
        {
            AmountDispensed = atm.ProcessRequest(account, amount);
        }
        [DSL]
        decimal enough_money()
        {
            return 1m;
        }
        [DSL(@"the_ATM_should_dispense_(?<amount>.+)")]
        bool dispensedShouldequal(decimal amount)
        {
            return AmountDispensed == amount;
        }
        [DSL(@"the_account_balance_should_be_(?<amount>.+)")]
        bool accbalShouldequal(decimal amount)
        {
            return account.Balance == amount;
        }
        [DSL]
        bool the_ATM_should_not_dispense_any_money()
        {
            return AmountDispensed == 0;
        }
        [DSL]
        bool the_card_should_be_returned()
        {
            return atm.Card == null;
        }
        [DSL]
        bool the_ATM_should_retain_the_card()
        {
            return atm.RetainedCards.Contains(card);
        }
        [DSL(@"the_ATM_should_say_(?<message>.+)")]
        bool atmmsg(string message)
        {
            return (atm.Message??"").Replace(" ", "_").ToLower().Contains(message);
        }
    }
}

Make sure we run the 'testing' scenarios on each build of this project

OK, since we want to run our BDD steps each time we build, we need to add a post-build step.

In Visual Studio, you can do it like this:

  1. Menu: Project/Properties
  2. Build Events tab
  3. Put the following text in the post-build-event commandline:
"$(ProjectDir)\lib\Be.Corebvba.Aubergine.ConsoleRunner.exe" 
      "$(TargetPath)" > "$(TargetDir)output.txt"
"$(TargetDir)output.txt"
exit 0

This makes sure that if the project is built successfully, the scenarios are also ran.

Now, on with the domain implementation

Since we now know exactly what we have to implement in the domain code in order to make sure our code does what the user specified, we can now write the domain code. If your code runs the specifications without failure, and the user approves the specs, then you can say that the code literally does what the user specified, so there can be no more discussions about such things...

Since this is an example, I will not implement enough code to make all tests pass, so you can try to extend the functionality of the domain classes yourself based on the Scenarios. Making those tests pass is thus left as an exercise to you, so you can grasp the whole idea about BDD by doing it yourself.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Com.CodeProject.BDDExample
{
    class Account
    {
        public Decimal Balance { get; set; }
        public decimal WithDrawCash(decimal amount)
        {
            if (amount > Balance) 
                return 0;
            else
            {
                Balance -= amount;
                return amount;
            }
        }
    }
    class Card
    {
        public CardState State { get; set; }
    }
    class ATM
    {
        public Card Card { get; set; }
        public Decimal AvailableCash { get; set; }
        public IList<Card> RetainedCards { get; private set; }
        public string Message { get; private set; }

        public ATM()
        {
            RetainedCards = new List<Card>();
        }

        public Decimal ProcessRequest(Account account,decimal amount)
        {
            var cash = account.WithDrawCash(amount);
            if (cash == 0)
                Message = "There are insufficient funds available on your account";
            else
                Message = "Please take your cash";
            Card = null;
            return cash;
        }
    }

    enum CardState
    {
        Valid,
        Disabled
    }
}

So why did I do all of this stuff?

If you want to know why you had to do all this stuff, it should be obvious once you "Build" your project; the following text should appear on your screen:

==STORY================================================================
Account_Holder_withdraws_cash => NOK
========================================================================
   Account_has_sufficient_funds => OK
      Given the_account_balance_is_100 => OK
      Given the_card_is_valid => OK
      Given the_machine_contains_enough_money => OK
      When the_Account_Holder_requests_20 => OK
      Then the_ATM_should_dispense_20 => OK
      Then the_account_balance_should_be_80 => OK
      Then the_card_should_be_returned => OK
   Account_has_insufficient_funds => OK
      Given the_account_balance_is_10 => OK
      Given the_card_is_valid => OK
      Given the_machine_contains_enough_money => OK
      When the_Account_Holder_requests_20 => OK
      Then the_ATM_should_not_dispense_any_money => OK
      Then the_ATM_should_say_there_are_insufficient_funds => OK
      Then the_account_balance_should_be_10 => OK
      Then the_card_should_be_returned => OK
   Card_has_been_disabled => NOK
      Given the_card_is_disabled => OK
      When the_Account_Holder_requests_20 => OK
      Then the_ATM_should_retain_the_card => NOK
      Then the_ATM_should_say_the_card_has_been_retained => NOK

Aha!! Seems like my software almost matches the specs the user required. Now it is your turn to make sure it matches the specs completely (i.e., let the Card_has_been_disabled pass)...

Please note that you have to close the editor displaying the text before you can go back to Visual Studio.

Points of interest

This is one example using my very own framework.

While I am an avid fan of my own stuff - ROFL -, I have to admit that it still is in constant development, and might not be considered production-ready yet (i.e., no unit testing/build server integration options etc.).

People looking for a more mature framework BDD/.NET could go for:

Final words

If you have any opinions or comments on this article that I might find interesting, please post them, so I can improve it!! Good luck doing BDD!!

History

  • 2009/11/09 - First public release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Virtual Sales Lab
Belgium Belgium

Comments and Discussions

 
Questionexperience with SpecFlow or Concordion.NET? Pin
Member 1117466023-Oct-14 2:07
Member 1117466023-Oct-14 2:07 
NewsI posted a new version of the article (way more details and up to date) Pin
Tom Janssens12-Aug-10 1:37
Tom Janssens12-Aug-10 1:37 
You can find it here:
http://www.codeproject.com/Articles/101090/Getting-started-quickly-with-BDD-Behaviour-driven-.aspx[^]

GeneralMajor bugfix + download available Pin
Tom Janssens17-Dec-09 22:47
Tom Janssens17-Dec-09 22:47 
NewsSourcecode is released, together with a manifest : Pin
Tom Janssens22-Nov-09 23:44
Tom Janssens22-Nov-09 23:44 
Newsv0.07 is released : support for "Given I did"-clause - article update still on todo-list Pin
Tom Janssens13-Nov-09 11:38
Tom Janssens13-Nov-09 11:38 
NewsFYI - Aubergine v0.06 is released - this article will be updated asap & please vote/comment [modified] Pin
Tom Janssens11-Nov-09 16:11
Tom Janssens11-Nov-09 16:11 
GeneralNice Pin
Stefano Ricciardi10-Nov-09 1:55
Stefano Ricciardi10-Nov-09 1:55 
GeneralRe: Nice Pin
Tom Janssens10-Nov-09 3:08
Tom Janssens10-Nov-09 3:08 
GeneralRe: Nice Pin
Stefano Ricciardi10-Nov-09 3:28
Stefano Ricciardi10-Nov-09 3:28 
QuestionCool - Where is this ATM located? Pin
Johnny J.9-Nov-09 21:55
professionalJohnny J.9-Nov-09 21:55 
AnswerRe: Cool - Where is this ATM located? [modified] Pin
Tom Janssens9-Nov-09 22:16
Tom Janssens9-Nov-09 22:16 
GeneralRe: Cool - Where is this ATM located? Pin
dzCepheus10-Nov-09 2:14
dzCepheus10-Nov-09 2:14 
GeneralRe: Cool - Where is this ATM located? Pin
Tom Janssens10-Nov-09 4:27
Tom Janssens10-Nov-09 4:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.