Getting started quickly with BDD in .Net

Introduction

This article will give you a brief introduction on how to get started with BDD (Behaviour driven development). It is my second attempt for such an article.

I will show you the complete workflow on how to write an app using BDD in .Net.

Background

Small note: if you want to know why you should use BDD, I would like to refer you to another one of my articles: The advantage of using BDD over TDD

Today I finally managed to upgrade my Aubergine BDD framework to v0.1.

This is now starting to get really usable (I've been dogfooding it for a while now).

This release contains the following changes:

  • Very basic support for NUnit
  • Build script using JeremySkinner's Phantom build engine
  • Properly named namespaces/dll's (i.e. no more Be.Corebvba.*)

In this spirit I decided to write a small tutorial on how to do BDD development in .Net, so maybe some people might find some use for this !!

Requirements

In the spirit of OSS development, I am going to show you how to develop an application using BDD together with SharpDevelop, an OSS IDE for .Net. You are offcourse free tu use any other IDE (i.e. Visual Studio, Monodevelop, ...)

Sharpdevelop

If you want to continue this tutorial using SharpDevelop, you can download it here (I used 3.2):

https://www.icsharpcode.net/opensource/sd/download/#SharpDevelop3x

In other IDE's the steps will probably be more or less the same, except that your screenshots will probably not match.

Aubergine

Next to this you also need my Aubergine BDD framework. You can download it here:

https://github.com/ToJans/Aubergine/zipball/master

If you want to, you can also take a quick peek at the source here:

https://github.com/ToJans/Aubergine

 

Getting started

Create a new empty solution in sharpdevelop, name it MyFirstBDD

 

Right click on the solution, and add a new project

Choose class library, and name it MyFirstBDD.Tests

Define your specs

This is a very simple example, but IRL you will have multiple txt-files with multiple stories and scenarios describing as much functionality as possible

Right-Click on the MyFirstBDD.Tests, and choose add a new file. For the file-type, select empty file, but name it specs.txt, and add the following content:

Define my own library
 using MyFirstBDD.Tests.MyOwnLibrary
  from MyFirstBDD.Tests.DLL

Story Manage my books
 Is about my own library
 
As an owner of books
I want be able to search for a keyword or part of the author/name of the book in my current books
So that I can avoid buying the same book multiple times.

Given I have the following books
+---+---------------------------------------+------------------------+--------------------+
| Nr| Title                                 | Author                 | Keywords           |
+---+---------------------------------------+------------------------+--------------------+
| 1 | Einsteins Schleier                    | Anton Zeilinger        | Quantum physics    |
| 2 | De kunst van het overtuigen           | Hans Christian Altmann | Business,NLP       |
| 3 | Harry Potter and the return of Peewee | Joske Vermeulen        | Fun, Eglantier     |
| 4 | Teititatutei                          | Joske Vermeulen        | Geen fun, Eglantier|
| 5 | Een twee drie                         | Joske Vermeulen        | fun, Eglantier     |
+---+---------------------------------------+------------------------+--------------------+
 
Scenario search for a single matching word
  When I search for "een"
  Then it should show 1 book
  And it should not return book nr 4

Scenario search for a single matching word with a wildcard
  When I search for "*een"
  Then it should return 2 books
  And it should return a book with the title "Teititatutei"

This defines what we want our app to do.

Define your BDD context

Now the we know what we want, we will  write our spec executors. These are simple implementations which will not compile yet (since we do not have a program yet).

These will help you to think about your interfaces you will expose from your program.

We identify all possible Given/When/Then steps, and make sure we gave some code for it, and also define the necessary interfaces:


using System;
using System.Collections.Generic;
using Aubergine.Model;
using System.Linq;

namespace MyFirstBDD.Tests
{
    public class Book
    {
        public int Id {get;set;}
        public string Title {get;set;}
        public string Author {get;set;}
        public IEnumerable<string> Keywords {get;set;}
    }
   
    public interface ILibrary
    {
        IEnumerable<Book> AllBooks {set;}
        IEnumerable<Book> Search(string searchstring);
    }
   
    public class MyOwnLibrary
    {
        ILibrary Lib=null;
        IEnumerable<Book> FoundBooks=null;
        
        [DSL]
        void i_have_the_following_books(int[] nr,string[] title,string[] author,string[] keywords)
        {
            IList<Book> l = new List<Book>();
            for (int i=0;i<nr.Length;i++)
            {
                l.Add(new Book() {
                          Id=nr[i],
                          Title = title[i],
                          Author = author[i],
                          Keywords = keywords[i].Split(',')
                      });
            }
            Lib.AllBooks = l;
        }
        
        [DSL("I search for \"(?<what>.+)\"")]
        void Search(string what)
        {
            FoundBooks = Lib.Search(what).ToArray();
        }
        
        [DSL("it should (?<not>not )?(return|show) (?<amount>\\d+) book(s?)")]
        bool Returnamount(string not,int amount)
        {
            return (FoundBooks.Count() == amount) ^ !string.IsNullOrEmpty(not);
        }
        
        [DSL("it should (?<not>not )?(return|show) a book with the title \"(?<title>.+)\"")]
        bool ShouldReturnBook(string not,string title)
        {
            return FoundBooks.Any(x=>x.Title == title)  ^ !string.IsNullOrEmpty(not);
        }

        [DSL("it should (?<not>not )?(return|show) book nr (?<nr>\\d+)")]
        bool ShouldReturnBook(string not,int nr)
        {
            return FoundBooks.Any(x=>x.Id == nr) ^ !string.IsNullOrEmpty(not);
        }
   }
}

Once finished, extract the book class, and move it to the main project (MyFirstBDD), under the model namespace. After doing this, you also extract and move the ILibrary interface to it's proper location (i.e. project MyFirstBDD, namespace Service)

Unit testing

Once you have done this, we need to setup unit testing; this is currently some kind of a hack, and it works as follows:

Add a class named Fixture to your test project and use the following code:


using System;
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;

namespace MyFirstBDD.Tests
{
    [TestFixture]
    public class Fixture : Aubergine.NUnitBDD.Fixture
    {
        public override IEnumerable<string> SpecFiles
        {
            get {
                yield return Path.Combine(Directory.GetCurrentDirectory(),"specs.txt");
            }
        }
       
        public Fixture()
        {
        }
    }
}

Also make sure you change the properties to copy "always" and build action "content". Also add a reference to the needed libs (both aubergine and project references)

Change your project type for myFirstBDD from "console project" to "Class library" in the properties and everything should compile !!

Right click on the fixture class definition in the source code, and you should see the context option "Unit test" available

 

If you do run the tests, it will say no tests are ran yet, but we'll fix that soon...

Implement the application

Up next is the implementation of the application; since this is only an example, I will simply implement the necessary stuff as simple as possible:


using System;
using System.Collections.Generic;
using System.Linq;
using MyFirstBDD.Model;
using System.Text.RegularExpressions;

namespace MyFirstBDD.Service.Impl
{
    public class Library : ILibrary
    {
        public Library()
        {
        }
       
        public IEnumerable<Book> AllBooks { set; protected get;}
       
        public IEnumerable<Book> Search(string searchstring)
        {
            var re = new Regex(@"\s"+Regex
                               .Escape(searchstring)
                               .Replace(Regex.Escape("*"),"(.*)")
                               .Replace(Regex.Escape("?"),".")
                               +@"\s");
            foreach(var b in AllBooks)
            {
                var kw = string.Join(" ",b.Keywords.ToArray());
                var arr = " "+b.Author+" "+kw+" " + b.Title+" ";
                if (re.IsMatch(arr))
                    yield return b;
            }
        }
    }
}

Now setup your test fixture

Make sure your test code now uses this code. In this case we only have a single dependency, and we use it directly. a typical real-life example would have multiple dependencies, and use probably some mocks/stubs instead of the real service, in order to test only the part you are interested in...


    public class MyOwnLibrary
    {
        public MyOwnLibrary()
        {
        }
       
        ILibrary Lib=new Service.Impl.Library();
        IEnumerable<Book> FoundBooks=null;
       
        [DSL]
        void i_have_the_following_books(int[] nr,string[] title,string[] author,string[] keywords)
        {
    ...

And execute your testcode (with coverage percentage if you want too)

Right click on the fixture class definition, unit test, unit test with coverage...

If everything went well, you should see the following (similar) output:

 

So you can see everything passed, and we have got a whopping 100% code coverage !!! (not hard in this case, but hey :-) )

We can now start on our UI, but that is a whole different story (Weak event messagebroker comes to mind for example)

In conclusion

While this is by no means a complete guide on BDD development, this should be enough to get you started.

If you like this, or have some remarks, please do let me know !!!

For any other articles on BDD on this blog, you can follow this link: corebvba - BDD

 

Wait, what about a ready-built solution ?

Here you go:

MyFirstBDD.zip (72,79 kb)

Enjoy !

Bookmark and Share