SpecFlow – getting up and running

SpecFlow is by far the BDD framework I like the best in the .NET ecosystem. It uses gherkin as natural language to describe features and scenarios. It has really good integration with visual studio. Under the hood it uses T4 templates to generate code that can be run using your favorite test runner like NUnit, MSTest.

To get started, start by downloading SpecFlow and install it. It gives you three new items to create in Visual Studio.

Start by creating an empty assembly.

Create a new item – choose SpecFlow Feature.
specflow1

SpecFlow creates an example feature for you.
specflow2

It describes a calculator operation. For the purpose of this exercise, which is to get all up and running we will fulfill this scenario. Every time you save the file SpecFlow will generate a cs file containing the tests for the scenarios. If you want to regenerate it you can always right click the feature file and choose “Run Custom Tool”

Start by adding references to TechTalk.SpecFlow.dll (it installs to C:\Program Files\TechTalk\SpecFlow on my computer) and if you are using NUnit like me also reference nunit.framework.dll
specflow3

Your project should now compile.

Run your tests. You should get the results:
specflow4

These are the actual methods the autogenerated tests expects to call to.

The next step is to create a Step definition file – which is a regular class with some attributes on it. This class will contain all the Arrange – Act – Assert code for your feature. There is a template for this too.
specflow5

You will get a stub file with some logic.

Lets look at the code and try to implement the scenario. We will take more of a TDD like approach since the calculator is more of a technical problem then business problem. Of course we would ideally be writing specifications coming from a business perspective on a more real-world-like application but that is a bit more complex and better suited for another blog post.

The code generated by the template for us:

using TechTalk.SpecFlow; namespace Features { [Binding] public class SpecFlowDemoSteps { [Given("I have entered (.*) into the calculator")] public void GivenIHaveEnteredSomethingIntoTheCalculator(int number) { //TODO: implement arrange (recondition) logic // For storing and retrieving scenario-specific data, // the instance fields of the class or the // ScenarioContext.Current // collection can be used. // To use the multiline text or the table argument of the scenario, // additional string/Table parameters can be defined on the step definition // method. ScenarioContext.Current.Pending(); } [When("I press add")] public void WhenIPressAdd() { //TODO: implement act (action) logic ScenarioContext.Current.Pending(); } [Then("the result should be (.*) on the screen")] public void ThenTheResultShouldBe(int result) { //TODO: implement assert (verification) logic ScenarioContext.Current.Pending(); } } }

This actually gives us somewhere to start in the startup scenario. If you choose not to use the stubbed code (which is only good for the dummy scenario), just look at the test runner results. The output actually gives us these methods to just copy / paste into the step definition file. As you might have guessed the regular expressions in the attributes maps to the parameters for the methods, very handy as we can reuse lots of code.

If we rerun the scenario with the suggested steps we get a different error suggesting we haven’t implemented any code to fulfill the steps.

Lets start:

private Calculator calculator = new Calculator();

        [Given("I have entered (.*) into the calculator")]
        public void GivenIHaveEnteredSomethingIntoTheCalculator(int number)
        {
            calculator.Enter(number);
        }

Of course those won’t compile. wee need to implement the calculator:

public class Calculator
    {
        public void Enter(int number)
        {
            
        }
    }   

Not much functionality but it should compile.

If we rerun our tests we get a message saying we should implement WhenIPressAdd

Well – let’s do that:

 [Binding]
    public class SpecFlowDemoSteps
    {
        private Calculator calculator = new Calculator();
        private int calculatorResult = 0;

        [Given("I have entered (.*) into the calculator")]
        public void GivenIHaveEnteredSomethingIntoTheCalculator(int number)
        {
            calculator.Enter(number);
        }

        [When("I press add")]
        public void WhenIPressAdd()
        {
            calculatorResult = calculator.Add();
        }

        [Then("the result should be (.*) on the screen")]
        public void ThenTheResultShouldBe(int result)
        {
            //TODO: implement assert (verification) logic

            ScenarioContext.Current.Pending();
        }
    }

    public class Calculator
    {
        public void Enter(int number)
        {

        }

        public int Add()
        {
            
        }
    } 

When we rerun our tests we are told to implement ThenTheResultShouldBe

Lets do it:

[Then("the result should be (.*) on the screen")]
        public void ThenTheResultShouldBe(int result)
        {
            Assert.That(calculatorResult, Is.EqualTo(result));
        }

Of course we get a failing test telling us error: Expected: 120 But was: 0

Looks like we will have to do some functionality now:

using NUnit.Framework;
using TechTalk.SpecFlow;
using System.Collections.Generic;

namespace Features
{
    [Binding]
    public class SpecFlowDemoSteps
    {
        private Calculator calculator = new Calculator();
        private int calculatorResult = 0;

        [Given("I have entered (.*) into the calculator")]
        public void GivenIHaveEnteredSomethingIntoTheCalculator(int number)
        {
            calculator.Enter(number);
        }

        [When("I press add")]
        public void WhenIPressAdd()
        {
            calculatorResult = calculator.Add();
        }

        [Then("the result should be (.*) on the screen")]
        public void ThenTheResultShouldBe(int result)
        {
            Assert.That(calculatorResult, Is.EqualTo(result));
        }
    }

    public class Calculator
    {
        private List<int> values = new List<int>();

        public void Enter(int number)
        {
            values.Add(number);
        }

        public int Add()
        {
            if(values.Count > 1)
            {
                return values[values.Count-1] + values[values.Count - 2];
            }
            
            return values[0];
            
        }
    } 
}

Silly implementation, I know but that is not the point.

Now it is time to write more scenarios to expand the functionality. Examples:

Scenario: Add three numbers

Given I have entered 50 into the calculator

And I have entered 70 into the calculator

And I have entered 10 into the calculator

When I press add

Then the result should be 130 on the screen

This actually compiles and turns up red since we already implemented steps that match the language we are using.

This goes on and on until we have written enough specs and code to fulfill them to have a working system.

If you want a pretty report you can generate one using

specflow.exe nunitexecutionreport MyProject.Specs.csproj /out:MyResult.html

Happy Speccing!

Leave a comment

1 Comments.