Tutorial – BDD and Dependency Injection in .Net (1)

This tutorial focuses on Behaviour Driven Development, Dependency Injection and application design in general. The primary technologies being used will be SpecFlow and MS Test. While we are targeting the .Net platform the content is applicable to most object oriented languages.

A Brief Introduction to Behaviour Driven Development

Behaviour Driven Development (BDD) is an ‘outside in’ or ‘top down’ development approach. Development starts from the user interface downwards as opposed to starting with the database design.  When there is no user interface (as in the beginning of this tutorial) we work from the highest level possible.

BDD encourages thin slices of work. Where we break features down into very small ‘end-to-end’ pieces of work. This work is expressed in the form of examples. This is known as specification by example and we will see this in the tutorial.

In SpecFlow speak, each example is known as a scenario. We need to implement a number of scenarios before the feature is considered complete. We will flesh out the design as we implement each scenario.

The following diagram from The RSpec Book shows the BDD cycle:

BDD Cycle

As depicted, there are two levels of testing – high level system or integration tests and lower level unit testing. The high level tests often drive a user interface and may be referred to as automated acceptance tests.

Not only will we be following the BDD cycle, but we will only implement the code we need to make the existing tests pass – and no more. We will be very strict with this. This may seem tedious to some however this approach is good to practice. You can always fall back to this approach should you become stuck when writing production code.

Interestingly TDD was always meant to have multiple levels of testing. In Extreme Programming Explained, Kent Beck and Cynthia Andres have the following to say about architects: “Architects on an XP team look for and execute large-scale refactorings, write system-level tests that stress the architecture, and implement stories”. This is similar to what we are trying to achieve in BDD. In fact Dan North introduced the term BDD to help him explain TDD to developers. This is why I say that BDD is TDD done properly.

These days it is common to hear teams say they are doing “TDD but not BDD”. By this they sometimes mean that they are writing system tests and unit tets, but they are not using a “BDD testing framework” like Cucumber or FitNesse. There is nothing wrong with this approach. Sometimes “we do TDD but not BDD’ means that the team members are not writing system level integration tests. This is better than no tests at all, however it is not true TDD and they are missing a trick.

A Brief Introduction to Dependency Injection

James Shaw does a great job of explaining Dependency Injection (DI). He points out that “Dependency Injection means giving an object its instance variables”. The instance variables are typically reference types which provide a layer of abstraction. We can give an object it’s instance variables through a number of different ways namely constructor injection, property injection, method injection.

There are many reasons for using Dependency Injection. The most obvious reason is that it makes the code more testable. It makes it easy to use mock objects in place of the real dependencies during testing. Testability is not the only reason, when done properly code which uses dependency injection tends to support the SOLID design principles, most notably the Single responsibility principle and Open/closed principle. This will be shown in the tutorial.

Dependency Injection is a simple concept, the confusion seems arise when Dependency Injection containers enter the discussion. For this reason I will not use DI Containers for the first few parts of this tutorial. All I will say is that DI Containers may help when composing objects, however they are not required to implement the dependency injection pattern.

The Tutorial – Part 1

When I began playing cricket at school, the coach instructed me to change the way I gripped the bat. At first the new grip seemed strange and uncomfortable. After many practice sessions I began hitting the ball further with more accuracy while using less effort. When I adopted TDD I went through a similar experience. I first felt discomfort and frustration but after practising the technique I found I was achieving better results with less effort.

In this tutorial we will build a Rock Paper Scissors game. We will focus on building a library that can be used with any front end e.g. a console application, WPF etc.

Part 1 covers the solution set up and the basics of SpecFlow. In this first section we are cover the set up and we get used to the BDD cycle. There is no dependency injection in this part, we will move onto that and more interesting topics in part 2. I will not spend much time explaining SpecFlow, we will get the hang of it as we go.

Solution Setup

Firstly download and install TechTalk.SpeckFlow. We will also need the latest version of Moq.

We begin by creating a new project in Visual Studio. Select a Class Library (C#) template. Name it RockPaperScissors. Class1 can be deleted.

Add a new project to the solution. This should be a Test Project (C#), call it RockPaperScissorsTest. UnitTest1 can be deleted. Add the two new folders called Tests and Specs. Then add a ‘Steps’ folder under the ‘Specs’ folder, see image below.

The Tests folder will contain the unit tests, the Specs and Steps folders are used for SpecFlow tests.

Add a reference to the latest version of Moq, TechTalk.SpeckFlow and the RockPaperScissors class library.

Solution Setup

SpecFlow uses NUnit by default. In order to use SpecFlow with MS Test we need to add an App.config file to the test project. Modify the App.config file to look as follows:

App.config

<!--?xml version="1.0" encoding="utf-8" ?-->

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section
       name="specFlow"
       type="TechTalk.SpecFlow.Configuration.ConfigurationSectionHandler, TechTalk.SpecFlow"/>
  </configSections>
  <specFlow>
    <unitTestProvider name="MsTest" />
  </specFlow>
</configuration>

Enter SpecFlow

Add a new a SpecFlow Feature file, with the following:

PlayRoundFeature.feature

Feature: Play a single round of Rock Paper Scissors
	As a player
	I want to play a round
	So that I can test my luck

Scenario: Computer chooses Rock and the player chooses Paper
	Given the computer makes a secret choice of Rock
	When I choose Paper
	Then the result should be "Player Wins!"

This contains a Feature and a single Scenario. The Feature is the user story. The Scenario forms a part of the specification, see specification by example.

Next we map the feature file onto code. This is done by adding a SpecFlow step definition file. Add a step definition file to the steps folder and call it ‘PlayRoundSteps.cs’. Delete all the sample steps.

No we need to define steps in the step definition file that match the lines within the feature file. The SpecFlow plugin makes this easy for us. In the feature file, right click on  the line “Given the computer makes a secret code of rock”, then choose ‘go to step definition’. This will give you an option to copy the generated code to the clipboard. Select copy and then past this step into the ‘PlayRoundSteps.cs’ file. Repeat this for other three lines in the feature file. At this stage the PlayRoundSteps.cs file should look as follows:

PlayRoundSteps.cs

        [Given(@"the computer makes a secret choice of Rock")]
        public void GivenTheComputerMakesASecretChoiceOfRock()
        {
            ScenarioContext.Current.Pending();
        }

        [When(@"I choose Paper")]
        public void WhenIChoosePaper()
        {
            ScenarioContext.Current.Pending();
        }

        [Then(@"the result shoussld be ""Player Wins!""")]
        public void ThenTheResultShoussldBePlayerWins()
        {
            ScenarioContext.Current.Pending();
        }

It is possible to run the SpecFlow tests by right clicking on the feature file and selecting ‘Run SpecFlow Scenarios’.

When using Cucumber, the steps are generated with a comment similar to “Now enter the code you wish you had” – and that is exactly what we need to do.

To start we remove ‘ScenarioContext.Current.Pending();’ from all steps and then implement the following code (which will not compile at this stage):

PlayRoundSteps.cs

        [Given(@"the computer makes a secret choice of Rock")]
        public void GivenTheComputerMakesASecretChoiceOfRock()
        {
            var game = new Game();
            ScenarioContext.Current.Add("Game", game);
        }

        [When(@"I choose Paper")]
        public void WhenIChoosePaper()
        {

        }

        [Then(@"the result should be ""Player Wins!""")]
        public void ThenTheResultShouldBePlayerWins()
        {
            var game = ScenarioContext.Current.Get("Game");
            Assert.AreEqual("Player Wins!", game.Result());
        }

The ScenarioContext is used to share objects between steps. At this stage the code cannot compile as we don’t have a Game class. Next we create this class in the RockPaperScissors assembly. Next we add the required method.

Game.cs

namespace RockPaperScissors
{
    public class Game
    {
        public string Result()
        {
            throw new NotImplementedException();
        }
    }
}

Now the code should compile and running the tests will throw a NotImplementedException. To fix this we drop down into unit testing.

Adding Unit Tests

In the Tests folder, create a unit test called GameTest.Add the following test:

GameTest.cs

        [TestMethod]
        public void ComputerRock_PlayerPaper_PlayerWins()
        {
            var game = new Game();
            Assert.AreEqual("Player Wins!", game.Result());
        }

At this stage we are duplicating the SpecFlow test in the unit test.There is little benefit at this stage. We are simply following the BDD cycle. Experience has taught me that it is better to introduce unit testing sooner rather than later. It is easier to remove redundant tests than to introduce unit testing late in development.

We are only adding what we really need. You are welcome to go faster if you wish, however I would like to show very small steps in this tutorial. Be patient, we are changing our grip…

The unit test should fail. Then we make it pass by adding the simplest bit of code we can in the Game class.

Game.cs

namespace RockPaperScissors
{
    public class Game
    {
        public string Result()
        {
            return "Player Wins!";
        }
    }
}

That should make the unit test pass. We then run the SpecFlow test, and that should also turn green.

At this stage I am not that interested in a complete implementation of the games’ rules, I am trying to flesh out the architecture. We know we are nowhere near a complete solution, but we do have our first scenario passing – progress!

Test Driven

We want to use tests to drive out the logic. We should write a test that exposes our hard coded “Player Wins!” line in the Game class. The easiest way to do that is to write a test that expects the computer to win. We move back up to high level testing and append another Scenario to the SpecFlow feature file:

PlayRoundFeature.feature

Feature: Play a single round of Rock Paper Scissors
	As a player
	I want to play a round
	So that I can test my luck

Scenario: Computer chooses Rock and the player chooses Paper
	Given the computer makes a secret choice of Rock
	When I choose Paper
	Then the result should be "Player Wins!"

Scenario: Computer chooses Rock and the player chooses Scissors
	Given the computer makes a secret choice of Rock
	When I choose Scissors
	Then the result should be "Computer Wins!"

We then add the two new step definitions:

PlayRoundSteps.cs

        [Given(@"the computer makes a secret choice of Rock")]
        public void GivenTheComputerMakesASecretChoiceOfRock()
        {
            var game = new Game();
            ScenarioContext.Current.Add(GameKey, game);
        }

        [When(@"I choose Paper")]
        public void WhenIChoosePaper()
        {

        }

        // Added
        [When(@"I choose scissors")]
        public void WhenIChooseScissors()
        {

        }

        [Then(@"the result should be ""Player Wins!""")]
        public void ThenTheResultShouldBePlayerWins()
        {
            var game = ScenarioContext.Current.Get(GameKey);
            Assert.AreEqual("Player Wins!", game.Result());
        }

        // Added
        [Then(@"the result should be ""Computer Wins!""")]
        public void ThenTheResultShouldBeComputerWins()
        {
            var game = ScenarioContext.Current.Get(GameKey);
            Assert.AreEqual("Computer Wins!", game.Result());
        }

The new test should fail with an appropriate error message e.g. Expected “Computer Wins!” actual “Player Wins!”. Now we drop back down into unit testing and replicate the test:

GameTest.cs


        [TestMethod]
        public void ComputerRock_PlayerPaper_PlayerWins()
        {
            var game = new Game();
            Assert.AreEqual("Player Wins!", game.Result());
        }

        [TestMethod]
        public void ComputerRock_PlayerScissors_ComputerWins()
        {
            var game = new Game();
            Assert.AreEqual("Computer Wins!", game.Result());
        }

This unit test should fail, similarly to the integration test we wrote using SpecFlow.

With the intention of taking small steps, we look for ways of doing the least amount of work to get the unit tests to pass. The computer chooses Rock in both tests, it is only the player choice that changes. Therefore we use that in our logic. First we alter the unit tests as follows:

GameTest.cs


        [TestMethod]
        public void ComputerRock_PlayerPaper_PlayerWins()
        {
            var game = new Game();
            game.PlayerMove = "Paper";
            Assert.AreEqual("Player Wins!", game.Result());
        }

        [TestMethod]
        public void ComputerRock_PlayerScissors_ComputerWins()
        {
            var game = new Game();
            game.PlayerMove = "Scissors";
            Assert.AreEqual("Computer Wins!", game.Result());
        }

Then we do the least amount of code in the Game class to make the tests pass. After refactoring, I ended up with:

Game.cs

namespace RockPaperScissors
{
    public class Game
    {
        public string PlayerMove { private get; set; }

        public string Result()
        {
            return PlayerMove == "Paper" ? "Player Wins!" : "Computer Wins!";
        }
    }
}

Later we will add more tests to drive out this rudimentary logic. At this stage we have the unit tests passing. We we still need to use the new PlayerMove property in the SpecFlow tests to get them passing. Before doing that it is a good idea to throw an exception if the PlayerMove property is used before it has been set. We drive this functionality out through tests:

GameTest.cs


        [TestMethod]
        public void ComputerRock_PlayerPaper_PlayerWins()
        {
            var game = new Game();
            game.PlayerMove = "Paper";
            Assert.AreEqual("Player Wins!", game.Result());
        }

        [TestMethod]
        public void ComputerRock_PlayerScissors_ComputerWins()
        {
            var game = new Game();
            game.PlayerMove = "Scissors";
            Assert.AreEqual("Computer Wins!", game.Result());
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ThrowsErrorIfPlayerMoveIsNotSet()
        {
            var game = new Game();
            game.Result();
        }

With the implementation being as follows:

Game.cs

namespace RockPaperScissors
{
    public class Game
    {
        private string _playerMove;
        public string PlayerMove
        {
            private get
            {
                if (String.IsNullOrEmpty(_playerMove))
                    throw new ArgumentNullException("PlayerMove");
                return _playerMove;
            }
            set
            {
                _playerMove = value;
            }
        }

        public string Result()
        {
            return PlayerMove == "Paper" ? "Player Wins!" : "Computer Wins!";
        }
    }
}

We then move onto the failing integration tests, they need to use the new property:

PlayRoundSteps.cs

namespace RockPaperScissorsTest.Specs.Steps
{
    [Binding]
    public class PlayRoundSteps
    {
        private const string GameKey = "Game";

        [Given(@"the computer makes a secret choice of rock")]
        public void GivenTheComputerMakesASecretChoiceOfRock()
        {
            var game = new Game();
            ScenarioContext.Current.Add(GameKey, game);
        }

        [When(@"I choose paper")]
        public void WhenIChoosePaper()
        {
            var game = ScenarioContext.Current.Get(GameKey);
            game.PlayerMove = "Paper";
        }

        [When(@"I choose scissors")]
        public void WhenIChooseScissors()
        {
            var game = ScenarioContext.Current.Get(GameKey);
            game.PlayerMove = "Scissors";
        }

        [Then(@"the result should be ""Player Wins!""")]
        public void ThenTheResultShouldBePlayerWins()
        {
            var game = ScenarioContext.Current.Get(GameKey);
            Assert.AreEqual("Player Wins!", game.Result());
        }

        [Then(@"the result should be ""Computer Wins!""")]
        public void ThenTheResultShouldBeComputerWins()
        {
            var game = ScenarioContext.Current.Get(GameKey);
            Assert.AreEqual("Computer Wins!", game.Result());
        }

    }
}

We now have a total of 5 tests passing, three unit tests and two SpecFlow integration tests. There is still a lot more to be done, including refactoring the SpecFlow tests to use the table feature. In upcoming posts we will see more of SpecFlow and we will use dependency injection.

Please let me know of any mistakes I have made, and any topics you would like to have covered in the follow up posts.

Advertisements

14 thoughts on “Tutorial – BDD and Dependency Injection in .Net (1)

  1. There is a lot of good content here! I am looking forward to the next post. Thanks for your hard work – Clair

  2. Thanks for the comments Clair. I am away for 2 weeks, so it may take about 3 weeks before the next post.

  3. Thanks for the resource.
    In line 21 of GameTests.cs you have
    var game = new Game(new DescissionEngine());

    Wich is causing an error because DescissionEngine is not implemented. Shoul I just remove it or have you/I missed some detail?

  4. Rcdmk, I am glad you are enjoying this. I will make time to post more on this topic.

    I think I was getting ahead of myself, please use the default constructor. This has now been fixed in the post.

    Thanks for your feedback!

  5. Pingback: Tutorial – BDD and Dependency Injection in .Net (2) | The Ramblings of Daryn Holmes

  6. Pingback: Tutorial – BDD and Dependency Injection in .Net (2) | The … | The Scissors Will

  7. Pingback: Tutorial – BDD and Dependency Injection in .Net (3) | The Ramblings of Daryn Holmes

  8. How come I am getting this error: The type arguments for method ‘TechTalk.SpecFlow.SpecFlowContext.Get(string)’ cannot be inferred from the usage. Try specifying the type arguments explicitly.

    With
    var game = ScenarioContext.Current.Get(GameKey);
    Assert.AreEqual(“Player Wins!”, game.Result());

  9. var game = ScenarioContext.Current.Get(GameKey);
    Assert.AreEqual(“Player Wins!”, game.Result());

    Try this it will work.

  10. var game = ScenarioContext.Current.Get(GameKey);
    Assert.AreEqual(“Player Wins!”, game.Result());

  11. @astrology and @Nimish what was the change? You mean replace a literal, quoted string with a string constant? I don’t see how that would make the error go away?

    @dherm try this instead:

    var game = ScenarioContext.Current.Get(GameKey);

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s