The power of SpecFlow

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're using 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

And the scenarios

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     | 348      |

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

Or another scenarios:

@Orders
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     | 348      |

    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.

[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 = new List();
	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 orders)
{
    var orderResult = new OrderResult();
    foreach (var order in orders)
    {
        var product = sessionManager.Query()
            .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 GitHib

Comments

Popular posts from this blog

Validate Automapper configurations with AssertConfigurationIsValid

Using moq to test class that extends abstract class