facebook

Blog

Stay updated

How to pass from requirements description to tests in .NET with BDD and SpecFlow
From requirements to testing with SpecFlow
Wednesday, May 08, 2019

Foreword

In past few months, I casually analyze the employment of frameworks and codes testing methods. In particular, I am using two code writing methods, which are intriguing me a lot: the TDD (Test Driven Development) and the BDD (Behaviour Driven Development). In this article, I will talk you about a framework called SpecFlow, which will help you to easily make BDD.

SpecFlow

SpecFlow is a testing framework supporting BDD. It allows to define tests based on the behavior that our feature should have , using a simple grammar through a language called Gherkin. To do so, it uses a file type with feature extension. Below you can find an example:

Feature: Batmobile Weapons
    In order fight villains
    As Batman
    I want to fire weapons from Batmobile.
 
Scenario: Fire Cannon successfully
    Given I see villain
    When I press fire cannon button
    Then Batmobile cannons fire

In the feature file, you need to describe first which would be the function of the feature to test, and then you can specify different scenarios (use case in given-when-then form) on whom you will test it. It’s clear that this method to write tests allows you not only to test the written code, but also permits you to support with precision our feature’s environment. Let’s use SpecFlow with an instance within Visual Studio.

Installation and setup

The installation of SpecFlow consists of two steps: 1) install the extension to Visual Studio 2017 and 2) create a project and configure it to be used with SpecFlow.

To install the extension, we go in the Tools menu and select Extensions and updates

Now we can create a new project, a MSTest Test Project type

Once the project has been created, right click on solution and choose manage NuGet package for the solution and then we install following packages: 1) SpecFlow 2) SpecFlow.Tools.MsBuild.Generation 3) SpecRun.SpecFlow

Well, we can now create our first feature file!

Add a feature file

We now need to add a feature file to our project, which defines the specific feature and includes test scenarios. It’s very easy to add a feature file to the project: right click on the project, then “add” and “new element”. On the left, we find the choice to select SpecFlow templates: we choose SpecFlow Feature File

Now we have to define our features and scenarios: I take as example a case I create specifically to easily explain the procedure. Imagine a project, where you have to import some CSV files from an external source and suppose you need to verify if the imported data are congruent with the origin. This is the feature file:

Feature: Items
    Import files from external application
 
Scenario: Import a valid Items file
    Given the following specific Items file
    """
    ItemNo;CreatedOn;ModifiedOn;Disabled
    10045112022051066;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051064;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051062;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051070;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051071;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051072;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051073;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051074;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051075;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    """
    When the service is started
    Then the following records must be present in Items table
    | ItemNo            | CreatedOn                 | ModifiedOn                | Disabled | 
    | 10045112022051066 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051064 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051062 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051070 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051071 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051072 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051073 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051074 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051075 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        |

In this file, our feature Items only import input files from an external application. Our scenario, on the contrary, specifies that:

  • Given: when we have a set of input values with a specific structure within the CSV itself (ItemNo;CreatedOn;ModifiedOn;Disabled);
  • When: we are waiting for the service to be started;
  • Then: the values made explicit in the table Items whose correspond to those present on the original CSV file.

It sounds good, but how can we now write the code that execute this test? It’s easy: just right click inside the feature file and choose Generating step definitions, in the context menu. Now insert a name for the binding class we are going to create and then click on “generate”. SpecFlow creates a class with this structure:

[Binding]
public class ItemsSteps
{
    [Given(@"the following specific Items file")]
    public void GivenTheFollowingSpecificItemsFile(string multilineText)
    {
        ScenarioContext.Current.Pending();
    }
 
    [When(@"the service is started")]
    public void WhenTheServiceIsStarted()
    {
        ScenarioContext.Current.Pending();
    }
 
    [Then(@"the following records must be present in Items table")]
    public void ThenTheFollowingRecordsMustBePresentInItemsTable(Table table)
    {
        ScenarioContext.Current.Pending();
    }
}

As we can see, every step in the feature file is now bound on a method, on which we must implement what we need for the test execution. We are ready for the first test execution! Draft our solution and in the menu select Test -> Window -> Explore Test, then click on Run all. If the test ends well, we find in the Test Explorer a green dot on all tests. Now we need to insert some code inside our Binding Class.

[Binding]
public class ItemsSteps
{
    List<string> itemList = new List<string>();
 
    [Given(@"the following specific Items file")]
    public void GivenTheFollowingSpecificItemsFile(string multilineText)
    {
        File.WriteAllText(
            Path.Combine(@"c:\temp", $"TEST_items.csv"),
            multilineText);
    }
 
    [When(@"the service is started")]
    public void WhenTheServiceIsStarted()
    {
        MyService service = new MyService();
        service.Start();
    }
 
    [Then(@"the following records must be present in Items table")]
    public void ThenTheFollowingRecordsMustBePresentInItemsTable(Table table)
    {
        foreach(var item in table.Rows)
        {
            Assert.IsTrue(FakeDatabase.ItemTable.Any(i => i.ItemNo == item[0]));
        }
    }
 
    [AfterTestRun]
    public static void AfterTestRun()
    {
        File.Delete(@"c:\temp\TEST_items.csv");
    }
}

Summarizing:

  1. we create our CSV in the GivenTheFollowingSpecificItemsFile method with the information contained in the feature file
  2. we start our service in the WhenTheServiceIsStarted, which only read items in the CSV and save them somewhere
  3. in the method ThenTheFollowingRecordsMustBePresentInItemsTable we control if the rows are congruent with the table in the feature file

We added the method AfterTestRun to run the test clean-up and delete the CSV file that has been created during their execution. In following executions, we verify again that all tests would be run properly.

I wish I succeed to transmit you my curiosity for this framework. You can find some example code at this link.

See you at next article!