Click here to Skip to main content
15,881,380 members
Articles / Programming Languages / C#

The Power of SpecFlow

Rate me:
Please Sign up or sign in to vote.
4.08/5 (3 votes)
31 May 2020CPOL2 min read 9K   5   2
An introduction to SpecFlow
In this article, I have given a quick introduction to SpecFlow.

Introduction

Writing test could be boring and stakeholders could know how your software should behave.

SpecFlow could help you. It's a framework for BDD that uses an extension of Visual Studio that transforms the feature asked by user into code to test.

SpecFlow allows you to write the behaviors in your native language and displays the data in a form like Excel table.

Install SpecFlow

You have to install the extension for Visual Studio in your test project. Install this package, nuget SpecFlow, SpecFlow.Tools.MsBuild.Generation, that will generate the code from our IDE and SpecFlow.xUnit (if you use xUnit) that will allow Visual Studio to find the test that specFlow will generate automatically.

Background

Suppose you want to create tests for a fruit and vegetable warehouse with online purchase.

However, be careful to split the sections correctly and it is what I'd like to dwell on.

Background is the section where you collect all that information that's common to every test scenario. For example, the list of users.

Feature: Order and warehouse testing
Background 
Given registered users 
| UserId   | Name  | Surname | Mail             | Delivery address | City     |
| AJ       | John  | Red     | j.red@red.com    | Down street      | London   |
| MWitch   | Marck | Witch   | Mark.Witch@gl.it | High street      | New york | 

If we were too detailed, the background part could be too large, you only need to write what you need to multiple scenarios.

We could put a @tag which allows us to collect features as if they were in a single namespace.

@Orders
Scenario: An order is submitted 

Given, When, Then

Given: Where we describe the precedent of the action we want to test:

Given The warehouse
| Code | Products | Quantity | Unit of measure | Alert threshold |
| P1   | Tomato   | 150      | Box             | 25              |
| V1   | Wine     | 350      | Bottle          | 40              |

When: Where we put the action that triggers the piece of code that we want to test:

When An order arrives
| User | Product | Quantity |
| AJ   | P1      | 2        |
| AJ   | V1      | 1        |

Then: Where we put everything that needs to happen when the code runs.

Then The warehouse contains these products
| Code | Product  | Quantity |
| P1   | Tomato   | 148      |
| V1   | Wine     | 349      |

Then the Purchasing Office is notified
| Product under threshold | Quantity | Threshold |

Or another scenarios:

@Order
Scenario: An order is placed that lowers the quantity of the products under the threshold
Given The warehouse
| Code | Products | Quantity | Unit of measure | Alert threshold |
| P1   | Tomato   | 26       | Box             | 25              |
| V1   | Wine     | 350      | Bottle          | 40              |

When An order arrives
| Users| Products | Quantity |
| AJ   | P1       | 2        |
| AJ   | V1       | 1        |

Then The warehouse contains these products
| Code | Products | Quantity |
| P1   | Tomato   | 24       |
| V1   | Wine     | 349      |

Then the Purchasing Office is notified
| Products under threshold | Quantity | Threshold |
| P1                       | 24       | 25        |

The Code

Now, you have to bind the table to a piece of code.

Here, we come to help the extension of spec flow for Visual Studio which translates into methods all the Given, When, Then that we entered. From the right-click specflow file, select Generate Step Definition.

You will need to use it to save data which are clearly fictitious, use an in-memory database, which will be populated for your Givens.

C#
[Given(@"registered users")]
public void GivenregisteredUsers(Table table) {
    foreach (var row in table.Rows)
    {
        sessionManager.AddRecord(
            new User
            {
                UserId = row["UserId"],
                Name = row["Name"],
                Surname = row["Surname"],
                DeliveryCity = row["City"],
                DeliveryAddress = row["Delivery address"],
                Mail = row["Mail"]
            });
        }
    }
}

When will respond to a code that will call the method we use to place the order:

C#
[When(@"An order arrives")]
public void WhenAnOrderArrives(Table table)
{
    OrderCore core = new OrderCore(sessionManager);
    List<<order>order> = new <List><order>();
    foreach (var row in table.Rows)
    {
        order.Add(
            new Order
            {
                User = row["User"],
                Product = row["Products"],
                Quantity = Convert.ToDecimal(row["Quantity"]),
            });
    }
    result = core.AcceptOrder(order);
}

with the code:

C#
public OrderResult AcceptOrder(IEnumerable<order> orders)
{
    var orderResult = new OrderResult();
    foreach (var order in orders)
    {
        var product = sessionManager.Query<product>()
            .Single(x => x.Code == order.Product);
        
        product.Quantity = product.Quantity - order.Quantity;
        sessionManager.SaveOrUpdate(product);

        if (product.Quantity < product.Threshold)
            orderResult.AlertThresholds.Add(
                new OrderResult.AlertThreshold
                {
                    product = product.Name,
                    Quantity = product.Quantity,
                    Threshold = product.Threshold
                });
    }

    return orderResult;
}

At Then, we will put some code that will check if the desired behaviours have been produced.

C#
[Then(@"The warehouse contains these products")]
public void ThenTheWarehouseContainsTheseProducts(Table table)
{
    var products = sessionManager.Query<Product>();
    foreach (var row in table.Rows)
    {
        var product = products.Where(x => x.Code == row["Code"]).Single();
        Assert.That(product.Quantity == Convert.ToDecimal(row["Quantity"]));
    }
}

[Then(@"the Purchasing Office is notified")]
public void ThenThePurchasingOfficeIsNotified(Table table)
{
    if (table.Rows.Count == 0)
        Assert.That(result.AlertThresholds.Count() == 0);
    else
    {
        Assert.That(result.AlertThresholds.Count() == table.Rows.Count);
        foreach (var row in table.Rows)
        {
            var product = result.AlertThresholds
                .SingleOrDefault(x => x.product == row["Products under threshold"]);

            Assert.That(product != null);
            Assert.That(product.Quantity == Convert.ToDecimal(row["Quantity"]));
            Assert.That(product.Threshold == Convert.ToDecimal(row["Threshold"]));
        }
    }
}

When you build your solution, Visual Studio will find the tests and show them in the Test explorer where you can run them from.

The sample code is on GitHub.

History

  • 22nd May, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Italy Italy
Alessandro is a software developer, graduated at politecnico of Milan with the passion for software programming, TDD and music.

Comments and Discussions

 
QuestionLost a bottle of wine? Pin
vcj#B4kT25-May-20 8:42
vcj#B4kT25-May-20 8:42 
AnswerRe: Lost a bottle of wine? Pin
Alessandro Magistroni31-May-20 8:23
Alessandro Magistroni31-May-20 8:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.