Tags

, , , ,

[UPDATE: 26-Nov-2011] Source code available @ GitHub. https://github.com/piredman/Zombie-BDD

[UPDATE: 24-Nov-2011] I have updated this example to use the SpecFlow’s ScenarioContext & moved all my assertions to “Then” methods. Thanks for these updates go to a couple of excellent comments by Darren Cauthon.

In a previous post I introduced Behaviour Driven Development (BDD) showing how this practice can simplify the discovery of requirements and provide an easily understood ubiquitous language for both stakeholders and developers.

In this post I will introduce a tool called SpecFlow and show how to take the behaviours you have captured and transform them into functional code.  SpecFlow is a .NET open source community tool that was inspired by the Ruby community tool, Cucumber.

“SpecFlow aims at bridging the communication gap between domain experts and developers by binding business readable behavior specifications to the underlying implementation.” – www.specflow.org

Before we get into the details of how to use SpecFlow let’s take a look at the specification that we will be implementing.

The Specification

We are going to use zombies for this example because, well, why not!  I have defined three (3) stories and their corresponding behaviours below.

Story: Zombies Eat People
In order to quench an unending hunger for human flesh
As a zombie
I want to eat people

Behaviour: Zombie Eats a Person
Given I have an animated zombie
And I have a living person
When I eat a person
Then the person should die

Story: People Attack Zombies
In order to avoid becoming lunch
As a person
I want to kill zombies

Behaviour: Person Attacks Zombie without a Weapon
Given I have a living person
And I do not have an equipped weapon
And I have an animated zombie
When I attack a zombie
Then the zombie should not die

Behaviour: Person Attacks Zombie with a Weapon
Given I have a living person
And I have an equipped weapon
And I have an animated zombie
When I attack a zombie
Then the zombie should die

Story: People Equip Weapons
In order protect myself from zombies
As a person
I want to use a weapon

Behaviour: Person Equips a Weapon
Given I have a living person
And I do not have an equipped weapon
When I equip a weapon
Then I should have an active weapon

Getting Started

To use SpecFlow you will need to download and install the latest version.  At the time of this posting, the latest iteration of SpecFlow is version 1.5.  Make sure you have closed all instances of Visual Studio as the installer will be adding new templates that we will be using shortly.

For the purposes of this demonstration, I will be using nUnit, a unit testing framework that will execute the SpecFlow behaviours.  You will need to have the latest version of this community tool installed as well, which is version 2.5.9 at this time.

One complimentary tool that I also recommend is TestDriven.net.  This community tool will also integrate with Visual Studio and provide you a context menu for initiating execution of your SpecFlow behaviours.

Setup the Project

Now that we have the tools we need installed, let’s fire up Visual Studio and get started building our first behaviour.  For the purposes of this tutorial I’m going to use simple class libraries, creating one for the SpecFlow behaviours and one for the actual implementation.

Since the SpecFlow behaviours will actually act as the acceptance tests for the implementation, I will name this class library “Zombies.AcceptanceTests” and a second class library, “Zombies” for the implementation.  We will need to add a project reference to the “Zombies” project from the “Zombies.AcceptanceTests” project so our acceptance tests can execute the implementations.

Before we can get started writing the first behaviour we need to add the appropriate assembly references to the acceptance test project.  Using the NuGet Package Manager Console, add SpecFlow and nUnit to the “Zombies.AcceptanceTests” project.  For an overview and tutorial of NuGet please refer to my previous post on the subject.

PM> Install-Package SpecFlow -ProjectName Zombies.AcceptanceTests
PM> Install-Package NUnit -ProjectName Zombies.AcceptanceTests
PM> Install-Package ShouldFluent -ProjectName Zombies.AcceptanceTests

This will download, install and setup each community library into the acceptance tests project.  You will notice that I threw in an extra library called “ShouldFluent”.  The Should Assertion Library provides a set of intuitive extention methods for asserting test results.  I will show how it is used once we start building the actual tests.

Creating a Feature

Now we are ready to start using SpecFlow.  When you bring up the
Add New Item” window for the acceptance tests project you will notice that there are three (3) new item templates available; SpecFlow Feature File, SpecFlow Step Definition & SpecFlow Event Definition.  At this point, we need to define the feature, so select the SpecFlow Feature File and name it “ZombiesEatPeople”.  You will be presented with a sample template; we want to replace the template with the following:

Feature: Zombies Eat People
In order to quench an unending hunger for human flesh
As a zombie
I want to eat people

Scenario: Zombie Eats a Person
Given I have an animated zombie
And I have a living person
When I eat a person
Then the person should die

At this point SpecFlow will auto-generate a class file behind the feature file.  You can inspect it by opening the ZombiesEatPeople.Feature.cs file collapsed behind the ZombiesEatPeople.feature file in the Solution Explorer.  You will notice that the “Designer generated code” is a series of nUnit tests.

It is important to note that, once you build the solution, your tests are actually executable at this point.  If you installed TestDriven.net you can right click on the acceptance test project, select “Test With” and choose “NUnit”.  Once the NUnit GUI will opens, hit the “Run” button and you will notice that the tests execute and you are presented with “Inconclusive” test results.  This is expected since we have not implemented the tests yet.

Now we need to implement the actual tests.  To do this, open the “Add New Item” window again, select “SpecFlow Step Definition” and name it “ZombiesEatPeopleSteps”.  My preference is to place all my Step Definitions into a single folder named “StepDefinitions”.  Once again you will be presented with a default implementation.  We need to replace this template with our actual steps.  SpecFlow and NUnit have made this quite simple.  Go back to or re-run the acceptance tests in NUnit.  Within the console app showing the “inconclusive” test results, navigate to the “Text Output” tab.  Once you do this you will notice that SpecFlow has not only displayed the error messages, but also stubbed out all the methods that you need to implement.  So, all you need to do is copy each method from the Text Output area and place them into your ZombiesEatPeopleSteps class.

When you have finished updating your Steps class it should look something like the following:

using TechTalk.SpecFlow;

namespace Zombie.AcceptanceTests.StepDefinitions
{
    [Binding]
    public class ZombiesEatPeopleSteps
    {
        [Given(@"I have an animated zombie")]
        public void GivenIHaveAnAnimatedZombie()
        {
            ScenarioContext.Current.Pending();
        }

        [Given(@"I have a living person")]
        public void GivenIHaveALivingPerson()
        {
            ScenarioContext.Current.Pending();
        }

        [When(@"I eat a person")]
        public void WhenIEatAPerson()
        {
            ScenarioContext.Current.Pending();
        }

        [Then(@"the person should die")]
        public void ThenThePersonShouldDie()
        {
            ScenarioContext.Current.Pending();
        }
    }
}

Now re-run the assembly in the NUnit test console and you should see the following output.

So, we have wired everything up and now ready to write our first test.  This is where BDD seamlessly becomes TDD (Test Driven Development).  You should notice how BDD makes determining what tests to write simple and straightforward.  So, using the conventions of TDD we will build out the class design & implementation based on the test that needs to be executed.

Now we need to fill in each Given, When & Then method with the appropriate test code.  As we do this we will stub out classes, properties and methods need to pass the test.  During this first pass, we do not want to actually implement any logic within these classes but just enough code to allow the solution to compile.  Really think about your design here.  Remember, this interface will dictate how your classes can be used.  Also, only add code that will allow you to create and assert the test, nothing more.

Here, I have shown my test implementations and the two classes I created in the “Zombies” assembly.  I have used the ScenarioContext object that is built into SpecFlow to store the state of member variables that will need to be used throughout the scope of this behaviour.  You will also notice my use of the “Should.Fluent” library within this code which allows for a really nice way to assert the test results.

Zombies Eat People Steps
using Should.Fluent;
using TechTalk.SpecFlow;

namespace Zombies.AcceptanceTests.StepDefinitions
{
    [Binding]
    public class ZombiesEatPeopleSteps
    {
        [Given(@"I have an animated zombie")]
        public void GivenIHaveAnAnimatedZombie()
        {
            var zombie = new Zombie();
            ScenarioContext.Current.Set(zombie);
        }

        [Given(@"I have a living person")]
        public void GivenIHaveALivingPerson()
        {
            var person = new Person();
            ScenarioContext.Current.Set(person);
        }

        [When(@"I eat a person")]
        public void WhenIEatAPerson()
        {
            var zombie = ScenarioContext.Current.Get<Zombie>();
            var person = ScenarioContext.Current.Get<Person>();

            zombie.Eat(person);
        }

        [Then(@"the person should die")]
        public void ThenThePersonShouldDie()
        {
            var person = ScenarioContext.Current.Get<Person>();
            person.IsAlive.Should().Be.False();
        }

    }
}
Zombie Class
using System;

namespace Zombies
{
    public class Zombie
    {
        public bool IsAnimated { get; set; }

        public void Eat(Person person)
        {
            throw new NotImplementedException();
        }
    }
}
Person Class
namespace Zombies
{
    public class Person
    {
        public bool IsAlive { get; set; }
    }
}

As you can see, my class implementations are minimalist and only contain enough code to allow the solution to compile.  Upon running the NUnit tests now you should notice that the tests are no longer inconclusive but are failing.  This is what we should expect and is actual goal at this time.

Write the test and watch it fail is the first step of the “Red, Green, Refactor” pattern described by TDD.  The next step is to implement the classes until the tests pass.  Let’s do that now.

Implemented Zombie Class
namespace Zombies
{
    public class Zombie
    {
        public bool IsAnimated
        {
            get; private set;
        }

        public Zombie()
        {
            IsAnimated = true;
        }

        public void Eat(Person person)
        {
            person.Die();
        }
    }
}
Implemented Person Class
namespace Zombies
{
    public class Person
    {
        public bool IsAlive
        {
            get; private set;
        }

        public Person()
        {
            IsAlive = true;
        }

        public void Die()
        {
            IsAlive = false;
        }
    }
}

As we discussed earlier, I have made some design decisions while implementing my classes.  First, I decided that when you create a Zombie or a Person, their default state is to be alive (or animated).  Next I also decided that the act of dieing should be controlled by the Zombie or Person in question.  For instance, the IsAlive property of a person can only be altered by the Person and not the attacking Zombie or another Person while the act of killing a Person is exposed by the Die method.

You should also notice that I have only implemented enough of each class to enable the test I am working on to pass.  I have not implemented any extra features or added any code that I might think I will need later.  Less code means less maintenance, fewer potential bugs and a simpler, easier to understand code base.  We will refactor these classes as we implement each additional test, again with this minimalist approach.

Create Another Feature

Right now, our zombies can eat people but the people are completely unable to defend themselves.  We have two (2) stories left to implement, “People Equip Weapons” and “People Attack Zombies”.  Let’s start with equipping weapons since the weapon a person is using will determine the outcome of their attack.

Here is the spec in question to refresh our memory.

Feature: People Equip Weapons
In order protect myself from zombies
As a person
I want to use a weapon

Scenario: Person Equips a Weapon
Given I have a living person
And I do not have an equipped weapon
When I equip a weapon
Then I should have an active weapon

All we need to do at this point is repeat the step described to implement our first story.  This time around though, we already have some existing tests and implemented classes, so while we implement new tests we might be required to update existing classes.  When this occurs you want to not only ensure your new tests pass but that your previous test still pass.  This completes the Red, Green, Refactor cycle of TDD.  So, the full lifecycle would look something like this:

  • Pick a story to implement.
  • Create the SpecFlow Feature file.
  • Ensure an “Inconclusive” test result.
  • Create the SpecFlow Step file
  • Copy the default missing step implementations from the SpecFlow test output into the SpecFlow Step file.
  • Ensure that the result is still “Inconclusive” but there are no missing step implementations.
  • Implement the Given, When & Then tests within the SpecFlow Step file.
  • Ensure all new tests “Fail”.
  • Create new or update existing classes, properties & methods to satisfy the new & existing tests.
  • Ensure all tests “Pass”.
  • Refactor.
  • Rinse and Repeat.

At this point our specification only states that a person can use weapons to kill zombies, in an attempt to keep this tutorial simple I have not defined any types of weapons.  So, if you have an equipped weapon, you will be able to kill zombies otherwise you are destined to become lunch.

Using the steps defined above, I have implemented the “People Equip Weapons” feature. Listed below is the feature implementation.

People Equip Weapons StepS
using Should.Fluent;
using TechTalk.SpecFlow;

namespace Zombies.AcceptanceTests.StepDefinitions
{
    [Binding]
    public class PeopleEquipWeaponsSteps
    {
        [Given(@"I have an equiped weapon")]
        public void GivenIHaveAnEquipedWeapon()
        {
            var person = new Person();
            person.EquipWeapon(new Weapon());
            ScenarioContext.Current.Set(person);
        }

        [Given(@"I do not have an equiped weapon")]
        public void GivenIDoNotHaveAnEquipedWeapon()
        {
            var person = new Person();
            person.EquipWeapon(null);
            ScenarioContext.Current.Set(person);
        }

        [When(@"I equip a weapon")]
        public void WhenIEquipAWeapon()
        {
            var person = ScenarioContext.Current.Get<Person>();
            person.EquipWeapon(new Weapon());
        }

        [Then(@"I should have an active weapon")]
        public void ThenIShouldHaveAnActiveWeapon()
        {
            var person = ScenarioContext.Current.Get<Person>();
            person.ActiveWeapon.Should().Not.Be.Null();
        }
    }
}

With the step file created I moved on to implementing the test methods. At this point our specification only states that a person can use weapons to kill zombies, in an attempt to keep this tutorial simple I have not defined any types of weapons. So, if you have an equipped weapon, you will be able to kill zombies otherwise you are destined to become lunch. Listed below is the feature implementation.

Weapon Class
namespace Zombies
{
    public class Weapon {}
}
Refactored Person Class
namespace Zombies
{
    public class Person
    {
        public bool IsAlive { get; private set; }
        public Weapon ActiveWeapon { get; set; }

        public Person()
        {
            IsAlive = true;
        }

        public void EquipWeapon(Weapon weapon)
        {
            ActiveWeapon = weapon;
        }

        public void Die()
        {
            IsAlive = false;
        }
    }
}

Now when you have successfully completed this feature you should have test results that look something like this.

Creating the Last Feature

Last, but certainly not least, we need to allow our people to fight back against the zombie hordes.  To do this we will need to repeat the previously defined steps once again for our last feature.

Feature: People Attack Zombies
In order to avoid becoming lunch
As a person
I want to kill zombies

Scenario: Person Attacks Zombie without a Weapon
Given I have a living person
And I do not have an equipped weapon
And I have an animated zombie
When I attack a zombie
Then the zombie should not die

Scenario: Person Attacks Zombie with a Weapon
Given I have a living person
And I have an equipped weapon
And I have an animated zombie
When I attack a zombie
Then the zombie should die

People Attack Zombies Steps
using Should.Fluent;
using TechTalk.SpecFlow;

namespace Zombies.AcceptanceTests.StepDefinitions
{
    [Binding]
    public class PeopleAttackZombiesSteps
    {
        [When(@"I attack a zombie")]
        public void WhenIAttackAZombie()
        {
            var person = ScenarioContext.Current.Get<Person>();
            var zombie = ScenarioContext.Current.Get<Zombie>();

            person.Attack(zombie);
        }

        [Then(@"the zombie should die")]
        public void ThenTheZombieShouldDie()
        {
            var zombie = ScenarioContext.Current.Get<Zombie>();
            zombie.IsAnimated.Should().Be.False();
        }

        [Then(@"the zombie should not die")]
        public void ThenTheZombieShouldNotDie()
        {
            var zombie = ScenarioContext.Current.Get<Zombie>();
            zombie.IsAnimated.Should().Be.True();
        }
    }
}

Refactored Person Class
namespace Zombies
{
    public class Person
    {
        public bool IsAlive { get; private set; }
        public Weapon ActiveWeapon { get; set; }

        public Person()
        {
            IsAlive = true;
        }

        public void EquipWeapon(Weapon weapon)
        {
            ActiveWeapon = weapon;
        }

        public void Attack(Zombie zombie)
        {
            if (null != ActiveWeapon)
                zombie.Die();
        }

        public void Die()
        {
            IsAlive = false;
        }
    }
}
Refactored Zombie Class
namespace Zombies
{
    public class Zombie
    {
        public bool IsAnimated { get; private set; }

        public Zombie()
        {
            IsAnimated = true;
        }

        public void Die()
        {
            IsAnimated = false;
        }

        public void Eat(Person person)
        {
            person.Die();
        }
    }
}

Following the steps defined above, I have added the Attack() method to the Person class and the Die() method to the Zombie class.  Also adding a check to the Attack() method to ensure that a person can only kill a zombie if they have an equipped weapon.

Conclusion

I hope this example has illustrated the benefits of Behaviour Driven Development (BDD).  Along side Test Driven Development (TDD) and Agile Software Development practices, BDD helps complete the development lifecycle picture.  I have found that the more I use this approach to building software, the more I love it.

If you have comments for improvements for this example, please let me know and I will update it accordingly.

Source

I have made the completed source code for this example available @ GitHub. https://github.com/piredman/Zombie-BDD

About these ads