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 Given
s.
[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:
[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:
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.
[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