Let’s assume an agile development team, from Developer to Product Owner, from Scrum Master to Stake Holder, all of them working collaboratively on product development.
Sounds great, doesn’t it? But unfortunately, this is not always 100% possible.
So, today we will talk about Behavior Driven Development.
Within the scope of this article, we will refresh our’s knowledge about BDD, after that, we will try to develop an API, that contains basic functions, with BDD approach on macOS using .NET Core and Visual Studio.
Obviously, in order to complete this article, I have been waiting for .NET Core support of SpecFlow for a long time.
In the context of this article, we will refer to the following topics:
BDD, which is based on the Test Driven Development (TDD), is especially concentrating on the producing of “quality code“.
Based on my ~2 years of experience, if I need to say that the great advantages of the BDD have provided us are, firstly it is an important tool against the difficulties that arise from the communication-related problems during the project development phase, and secondly, it provides us with the great project documentation.
Usually, a team, who will develop the project, is dependent on the business teams in order to understand all the needs of the customer correctly.
However, most of the time poorly written code and incomplete needs may appear due to the fact that business teams are far away from the technical side. At this point, BDD provides us a “common language” to solve these problems. In other words, it is acting as a guide to improve communication between developers, test teams and business teams and also to understand the requirements easily.
In addition, BDD clearly defines the behaviors, that will affect the business outcome directly, in a way that is accessible to the developers, test teams and the business teams. The focus of this definition in the BDD is to finding the requirements in the user story and writing the acceptance tests based on the requirements. That is, it draws a path for the development of the project in accordance with end-to-end acceptance criteria.
NOTE: In the BDD, customers also get involved in the development process.
In the BDD, acceptance criteria are defined as “scenarios”. The scenarios are structured and explain how a feature should behave in different situations or with different parameters.
E.g:
In addition, the scenarios are written in a linguistic format called “Gherkin“, which consists of the Given, When and Then sections.
As we can see, the scenarios are written in a simple form of speaking language, this is making them easy to understand by all team members. It also has the characteristic of documentation for the project.
You can reach more detailed information about this topic from here.
First of all, BDD is one of the methods used in test automation projects. In addition to using the test scenarios written in Gherkin format in the “automation process”, it also provides living and up-to-date documentation of the project.
In general, the benefits are:
Besides, BDD significantly reduces the time spent on “end user” and “user acceptance” tests in the software development process.
Let’s assume we are working in an e-commerce company. We are requested to develop an API so that users can add their favourite products to their favourite lists. Let’s develop this API with BDD and see how it works out.
I will develop the API on macOS using Visual Studio and .NET Core 2.2. Also, we will use SpecFlow as BDD framework.
SpecFlow is an open-source Behavior Driven Design framework that allows us to define and manage human-readable acceptance tests using the Gherkin parser.
Firstly, if you don’t have Visual Studio on macOS, you can download it here. After opening Visual Studio, let’s enter the “Extensions” tab then click to the “Gallery” tab. Now we need to type “Straight8’s SpecFlow Integration” in the search box and then install the related extension as follows.
With this extension, we will be able to add features and step definitions to our project easily.
Now let’s create a .NET Core 2.2 NUnit Test Project called “MyFavouriteAPI.Tests“. Then, we need to include “SpecFlow“, “SpecFlow.Tools.MsBuild.Generation” and “SpecFlow.NUnit” packages to the project via NuGet.
For general configuration options, we should create a configuration file called “specflow.json” as follows.
{ "language": { "feature": "en-US" } }
With this option, we specify that the feature files will be in English.
After creating the configuration file, let’s create a new folder called “Features“. Then add a new feature file in this folder called “FavouriteList” as follows.
The extension will create a template feature file as below.
Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers @mytag Scenario: Add two numbers Given I have entered 50 into the calculator And I have entered 70 into the calculator When I press add Then the result should be 120 on the screen
Now let’s create our own feature script by editing the contents of this template.
What we want as a feature is that “users can create own favourite list and add or remove products from the list in order to buy them later“.
So, let’s edit the feature as follows.
Feature: Favourite List A simple favourite list that we can add or remove products in order to buy them later
Now we can define our first scenario. Firstly, we need to create a favourite list. To do that, let’s called the scenario part as “create a new favourite list“.
So when this scenario will happen, what is the action here? For the definition of the action here, I think it is enough to say “when I create a new favourite list“. Now, what will be the result of this operation, what is the outcome here?
For this, we can say “then the favourite list should be created as empty“. Based on this scenario, let’s edit the feature file as follows.
Feature: Favourite List A simple favourite list that we can add or remove products in order to buy them later @mytag Scenario: Create a new favourite list When I create a new favourite list Then the favourite list should be created as empty
This scenario, which we have described above, clearly explains how the work will be done, doesn’t it? A simple language that can be used and understood by each member of the team.
After defining the scenario, let’s build the project and switch to the “Unit Tests” pad through the IDE.
Ahha! After building the project, the extension has created the “CreateANewFavouriteList” test for us.
So, let’s run the test and look at the result on the test result pad.
It gives us the “No matching step definition found for one or more steps.” message because we haven’t written any code yet.
Now we need to define the step definitions related to our scenario. For this, we can use the sample code snippet that the extension gave us in the result pad.
Let’s create a folder called “StepDefinitions” and define a class in this folder called “FavouriteListSteps“.
Then copy the sample code snippet and paste it into the “FavouriteListSteps” class as follows.
NOTE: Don’t forget to edit “MyNamespace” and “StepDefinitions” parts.
using System; using TechTalk.SpecFlow; namespace MyFavouriteAPI.Tests.StepDefinitions { [Binding] public class FavouriteListSteps { [When(@"I create a new favourite list")] public void WhenICreateANewFavouriteList() { ScenarioContext.Current.Pending(); } [Then(@"the favourite list should be empty")] public void ThenTheFavouriteListShouldBeEmpty() { ScenarioContext.Current.Pending(); } } }
It’s pretty clear what we are supposed to do here, right?
Before start to coding, we need to include the “FluentAssertions” package via NuGet to perform assertions easily.
Then, let’s start coding to our sample scenario as follows.
using TechTalk.SpecFlow; using System.Collections.Generic; using FluentAssertions; using System; using System.Linq; namespace MyFavouriteAPI.Tests.StepDefinitions { [Binding] public class FavouriteListSteps { private readonly IFavouriteService _favouriteService; private int _favouriteListId; private readonly int _userId; public FavouriteListSteps() { _favouriteService = new FavouriteService(); _userId = 1; } [When(@"I create a new favourite list")] public void WhenICreateANewFavouriteList() { _favouriteListId = _favouriteService.Create(_userId); } [Then(@"the favourite list should be empty")] public void ThenTheFavouriteListShouldBeEmpty() { FavouriteList favouriteList = _favouriteService.GetFavouriteList(_userId, _favouriteListId); favouriteList.Should().NotBeNull(); favouriteList.FavouriteListId.Should().Be(_favouriteListId); favouriteList.ProductIds.Should().BeNull(); } } public class FavouriteList { public int FavouriteListId { get; set; } public List<int> ProductIds { get; set; } } public interface IFavouriteService { int Create(int userId); FavouriteList GetFavouriteList(int userId, int favouriteListId); } public class FavouriteService : IFavouriteService { private readonly Dictionary<int, List<FavouriteList>> favouriteListStore = new Dictionary<int, List<FavouriteList>>(); public int Create(int userId) { int favouriteListId = new Random().Next(10); var newFavouriteList = new List<FavouriteList> { new FavouriteList { FavouriteListId = favouriteListId } }; favouriteListStore.Add(userId, newFavouriteList); return favouriteListId; } public FavouriteList GetFavouriteList(int userId, int favouriteListId) { if (favouriteListStore.TryGetValue(userId, out List<FavouriteList> userFavouriteList)) { var favouriteList = userFavouriteList.FirstOrDefault(_ => _.FavouriteListId == favouriteListId); return favouriteList; } return null; } } }
We simply coded “When” and “Then” steps. First, we have created a new favourite list for the user, then we verified that the favourite list is empty.
Now let’s run the “CreateANewFavouriteList” test again.
As we can see the test passed successfully.
Now we need to add one more new scenario to the feature. The user should be able to add products to his/her favourite list also delete them.
For that, let’s extend our scenario as follows.
Feature: Favourite List A simple favourite list that we can add or remove products in order to buy them later @mytag Scenario: Create a new favourite list When I create a new favourite list Then the favourite list should be empty Scenario: Add a new product to the favourite list Given I create a new favourite list When I select the favourite list and press the add favourite button on the product detail page Then the product should be added to the favourite list Scenario: Remove a product from the favourite list Given I create a new favourite list And I select the favourite list and press the add favourite button on the product detail page When I press the remove product button on the favourite list page Then the product should be removed from the favourite list
After saving the scenario, we need to rebuild the project and then take a look at the “Unit Tests” pad again.
After building the project, the extension has created the tests for the scenario, that we have defined newly, named “AddANewProductToTheFavouriteList” and “RemoveAProductFromTheFavouriteList“.
Let’s run the tests and take the sample method snippets on the test result pad again.
No matching step definition found for one or more steps. using System; using TechTalk.SpecFlow; namespace MyNamespace { [Binding] public class StepDefinitions { [Given(@"I create a new favourite list")] public void GivenICreateANewFavouriteList() { ScenarioContext.Current.Pending(); } [When(@"I select the favourite list and press the add favourite button on the product detail page")] public void WhenISelectTheFavouriteListAndPressTheAddFavouriteButtonOnTheProductDetailPage() { ScenarioContext.Current.Pending(); } [Then(@"the product should be added to the favourite list")] public void ThenTheProductShouldBeAddedToTheFavouriteList() { ScenarioContext.Current.Pending(); } } } No matching step definition found for one or more steps. using System; using TechTalk.SpecFlow; namespace MyNamespace { [Binding] public class StepDefinitions { [Given(@"I create a new favourite list")] public void GivenICreateANewFavouriteList() { ScenarioContext.Current.Pending(); } [Given(@"I select the favourite list and press the add favourite button on the product detail page")] public void GivenISelectTheFavouriteListAndPressTheAddFavouriteButtonOnTheProductDetailPage() { ScenarioContext.Current.Pending(); } [When(@"I press the remove product button on the favourite list page")] public void WhenIPressTheRemoveProductButtonOnTheFavouriteListPage() { ScenarioContext.Current.Pending(); } [Then(@"the product should be removed from the favourite list")] public void ThenTheProductShouldBeRemovedFromTheFavouriteList() { ScenarioContext.Current.Pending(); } } }
Now, in order to complete the feature, we need to implement these behaviors, which are expected from us, in the “FavouriteListSteps” class as follows.
using TechTalk.SpecFlow; using System.Collections.Generic; using FluentAssertions; using System; using System.Linq; namespace MyFavouriteAPI.Tests.StepDefinitions { [Binding] public class FavouriteListSteps { private readonly IFavouriteService _favouriteService; private int _favouriteListId; private readonly int _userId; private readonly int _productId; public FavouriteListSteps() { _favouriteService = new FavouriteService(); _userId = 1; _productId = 1; } [Given(@"I create a new favourite list")] [When(@"I create a new favourite list")] public void WhenICreateANewFavouriteList() { _favouriteListId = _favouriteService.Create(_userId); } [Then(@"the favourite list should be empty")] public void ThenTheFavouriteListShouldBeEmpty() { FavouriteList favouriteList = _favouriteService.GetFavouriteList(_userId, _favouriteListId); favouriteList.Should().NotBeNull(); favouriteList.FavouriteListId.Should().Be(_favouriteListId); favouriteList.ProductIds.Should().BeEmpty(); } [Given(@"I select the favourite list and press the add favourite button on the product detail page")] [When(@"I select the favourite list and press the add favourite button on the product detail page")] public void WhenISelectTheFavouriteListAndPressTheAddFavouriteButtonOnTheProductDetailPage() { _favouriteService.AddFavourite(_userId, _favouriteListId, _productId); } [Then(@"the product should be added to the favourite list")] public void ThenTheProductShouldBeAddedToTheFavouriteList() { FavouriteList favouriteList = _favouriteService.GetFavouriteList(_userId, _favouriteListId); favouriteList.Should().NotBeNull(); favouriteList.FavouriteListId.Should().Be(_favouriteListId); favouriteList.ProductIds.Should().Contain(_productId); } [When(@"I press the remove product button on the favourite list page")] public void WhenIPressTheRemoveProductButtonOnTheFavouriteListPage() { _favouriteService.RemoveProduct(_userId, _favouriteListId, _productId); } [Then(@"the product should be removed from the favourite list")] public void ThenTheProductShouldBeRemovedFromTheFavouriteList() { FavouriteList favouriteList = _favouriteService.GetFavouriteList(_userId, _favouriteListId); favouriteList.Should().NotBeNull(); favouriteList.FavouriteListId.Should().Be(_favouriteListId); favouriteList.ProductIds.Should().NotContain(_productId); } } public class FavouriteList { public int FavouriteListId { get; set; } public List<int> ProductIds { get; set; } } public interface IFavouriteService { void AddFavourite(int userId, int favouriteListId, int productId); int Create(int userId); FavouriteList GetFavouriteList(int userId, int favouriteListId); void RemoveProduct(int userId, int favouriteListId, int productId); } public class FavouriteService : IFavouriteService { private readonly Dictionary<int, List<FavouriteList>> favouriteListStore = new Dictionary<int, List<FavouriteList>>(); public void AddFavourite(int userId, int favouriteListId, int productId) { FavouriteList favouriteList = GetFavouriteList(userId, favouriteListId); if(favouriteList != null) { favouriteList.ProductIds.Add(productId); } } public int Create(int userId) { int favouriteListId = new Random().Next(10); var newFavouriteList = new List<FavouriteList> { new FavouriteList { FavouriteListId = favouriteListId, ProductIds = new List<int>() } }; favouriteListStore.Add(userId, newFavouriteList); return favouriteListId; } public FavouriteList GetFavouriteList(int userId, int favouriteListId) { if (favouriteListStore.TryGetValue(userId, out List<FavouriteList> userFavouriteList)) { var favouriteList = userFavouriteList.FirstOrDefault(_ => _.FavouriteListId == favouriteListId); return favouriteList; } return null; } public void RemoveProduct(int userId, int favouriteListId, int productId) { FavouriteList favouriteList = GetFavouriteList(userId, favouriteListId); if (favouriteList != null) { favouriteList.ProductIds.Remove(productId); } } } }
I would like to mention a few points here. If there is a similar scenario that we have implemented before, we don’t need to re-code it. All we have to do is add the “Given” context in the right place as in the feature file.
For example, to be able to add a new product into a favourite list or delete, first we need to create a favourite list. In order to do this, it will be enough to add “[Given(@”I create a new favourite list”)]” attribute to the “WhenICreateANewFavouriteList” method we have implemented before.
Then, we have implemented the behaviours expected from us. Now let’s go back to the “Unit Tests” pad and run all the tests.
Tada! All the scenarios’ tests, that are required to complete the “FavouriteList” feature, are passed successfully.
When talking about the benefits of BDD at the beginning of this article, we have mentioned the following topics:
Now let’s take a look at the feature file that we created.
Feature: Favourite List A simple favourite list that we can add or remove products in order to buy them later @mytag Scenario: Create a new favourite list When I create a new favourite list Then the favourite list should be empty Scenario: Add a new product to the favourite list Given I create a new favourite list When I select the favourite list and press the add favourite button on the product detail page Then the product should be added to the favourite list Scenario: Remove a product from the favourite list Given I create a new favourite list And I select the favourite list and press the add favourite button on the product detail page When I press the remove product button on the favourite list page Then the product should be removed from the favourite list
This feature file has a simple and understandable language that can be used by each member of the team and also it is up-to-date documentation of the project. During the development process, it guided our code by following the behaviour of the application.
BDD is an important methodology that can be used especially when cooperation is needed in product development. It focuses on the user and the behaviour of the application, thus it minimizes maintenance and additional efforts. It also supports the test automation process by creating up-to-date documentation of the project.
Link: https://github.com/GokGokalp/BDDSampleWithNetCoreSpecFlow
https://specflow.org/getting-started/
https://specflow.org/documentation/
https://specflow.org/2018/specflow-3-public-preview-now-available/
{:en}In today’s technological age, we typically build our application solutions on event-driven architecture in order…
{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…
{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…
{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…
{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…
{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…
View Comments
Thank you very much. very informative.
Thanks for your comment.
Thanks for the information,
I did add the feature file as mentioned but my feature file is not in color pattern.
I'm using MacBook Pro,
Visual Studio 2019
Spec flow, Selenium & Nunit extensions are downloaded along with the Straight8 Specflow extension and the specflow.json is added as well to the project.
Hi, maybe it can be related with versions of VS for mac or "Straight8’s SpecFlow Integration"?