Behavior Driven Development and Cucumber
|The aim of this article is to explain how to effectively use Cucumber to create automated user acceptance tests. The programming language and technology used will be C# and Cucumber.
Over recent years I have become more and more convinced about the benefits of BDD using tools like Cucumber. I regularly deliver training in all sorts of companies all over the world, and I have observed a few consistent trends.
- Teams want to do Test Driven Development, but often struggle to apply it in their own context.
- Teams want to automate testing, but in reality do not always do all that much of it.
- Teams want to improve quality, but then the deadline looms fast and that aspiration goes out of the window.
You can check out the YouTube video accompanying this posting here:
If you can relate to the observations above, then you might find the following helfpul as BDD with Cucumber based acceptance tests can assist in addressing the root causes of the issues outlined above. So here is an overview of how Cucumber can help us with automated tests for our acceptance testing.
Specifications, Examples and Tests all in One Document
One of the benefits of Cucumber is that requirements are captured in the form of examples. The example I often use is that of a speaking clock. In the UK we have a phone number you can ring to find out the current time (antiquated i know but we still have it!). If you call that number, it speaks out the time to you. But what does it say? How do we want it to phrase the current time? We could write a Use Case for it, we could write a detailed specification for all the possible variations, or we could simply capture some examples:
Actual time | What is said |
---|---|
12:00pm | Midday |
1:00pm | It is one o’clock in the afternoon |
2:30pm | It is half past two in the afternoon |
The more examples you have, the easier it is to understand what is required, and the easier it is for the business, the developers, and indeed any other stakeholders to understand what the system is meant to do. More importantly, tests can be created based on these requirements.
Requirements specified by example are so much easier for everyone, and they minimise the chances of ambiguity. Also, if anything gets missed, you can add it to the set of examples, right a test for it and then fix the issue.
These requirements can be captured in the form of Scenarios when using Cucumber which are captured in files referred to as Feature Files. Below is a feature file for a speaking clock:
Scenario: ConvertMultipleTimesToText Given I have been provided this set of times and expected results | Hours | Minutes | ExpectedText | | 0 | 0 | midnight | | 12 | 0 | midday | When I request all the times as text Then the text should match
Once a scenario has been created, everyone can review it to ensure that they understand what is then required of the system. The basic tabulated structure above easily allows for additional examples to be added in whenever there is confusion or ambiguity.
Writing the Automated Test
We have already discussed that requirements can be captured as a series of examples. These written examples can then easily be turned into automated tests. These tests can be used as a key part of the acceptance tests for the system. Does the system meet the requirements?
Let’s take our scenario written above. How can code be written to test this?
Cucumber supports many languages. We will use C# for now, but most mainstream languages are supported. Below is a C# class that will act as the actual test. It is written using SpecFlow which is a C# implementation for Cucumber.
namespace CucumberExample
{
[Binding]
public class SpeakingClockSteps
{
private DateTime dateTime;
private SpeakingClock speakingClock;
private string actualTimeAsText;
private List timeTables;
[Given(@"the time is (.*) hours and (.*) minutes")]
public void GivenTheTimeIsHoursAndMinutes(int hours, int minutes)
{
dateTime = new DateTime(2016,10,27,hours,minutes,0);
speakingClock = new SpeakingClock();
}
[When(@"I request the time")]
public void WhenIRequestTheTime()
{
actualTimeAsText = speakingClock.GetTimeAsText(dateTime);
}
[Then(@"the result should be (.*)")]
public void ThenTheResultShouldBeTimeAsText(string expectedTimeAsText)
{
Assert.AreEqual(expectedTimeAsText, actualTimeAsText);
}
[Given(@"I have been provided this set of times and expected results")]
public void GivenIHaveBeenProvidedThisSetOfTimesAndExpectedResults(Table tableOfTimes)
{
timeTables = tableOfTimes.CreateSet().ToList();
}
[When(@"I request all the times as text")]
public void WhenIRequestAllTheTimesAsText()
{
//ScenarioContext.Current.Pending();
}
[Then(@"all the text should match")]
public void ThenAllTheTextShouldMatch()
{
speakingClock = new SpeakingClock();
foreach (var currentTimeExample in timeTables)
{
DateTime time = new DateTime(2016,10,28,currentTimeExample.Hours, currentTimeExample.Minutes,0);
Assert.AreEqual(currentTimeExample.ExpectedText,speakingClock.GetTimeAsText(time));
}
}
}
}
For those of you unfamiliar with Cucumber, what is happening here is that the attributes above the methods contain regular expressions that match the contents of the text in the scenario. So for example, the scenario states, “Given I have been provided this set of times and expected results” which matches the pattern above the method:
[Given(@"I have been provided this set of times and expected results")]
In the method that contains this annotation, a parameter is passed in (which is part of the Cucumber implementation called SpecFlow). This parameter will automatically be populated with the data from the Scenario table you can see above. We can then easily convert this into a List of objects of our own custom type which contain hours, minutes, and the expected text as properties.
[Given(@"I have been provided this set of times and expected results")]
public void GivenIHaveBeenProvidedThisSetOfTimesAndExpectedResults(Table tableOfTimes)
{
timeTables = tableOfTimes.CreateSet().ToList();
}
Our property above called timeTables is a List objects, with the class being as follows:
public class TimeTable
{
public int Hours { get; set; }
public int Minutes { get; set; }
public string ExpectedText { get; set; }
}
This data can then be used in our method with the Then attribute. So ‘given’ this set up, ‘then’ we can assert our expected result.
So in the method using the Then attribute, we assert our expectations.
[Then(@"all the text should match")]
public void ThenAllTheTextShouldMatch()
{
speakingClock = new SpeakingClock();
foreach (var currentTimeExample in timeTables)
{
DateTime time = new DateTime(2016,10,28,currentTimeExample.Hours, currentTimeExample.Minutes,0);
Assert.AreEqual(currentTimeExample.ExpectedText,speakingClock.GetTimeAsText(time));
}
}
The ‘system’ we are testing here is the SpeakingClock. The asserts are written using normal MSTest methods.
Other Forms Of Test
In addition to working with classes in our automated tests, we can also interact with Web sites using tools like Selenium. This means that our acceptance test can actually exercise our Web front end.
Installing SpecFlow
You can download the project containing the code examples above from this link. To run the You will need to install the SpecFlow extension to Visual Studio. To install SpecFlow into Visual Studio 2015 you will need to:
- In Visual Studio 2015, click Tools, and then click Extensions and Updates.
- At the Extensions and Updates dialog, in the left pane, click Online, and then in the top right corner, enter SpecFlow in the search field.
- In the search results, find SpecFlow for Visual Studio 2015 and then click Install.
- Restart Visual Studio (sometimes this is not required, but I have observed that on some systems a restart is required).
Running the example solution
To run the example, open the SpeakingClock.feature file and then right click in the Scenario show above, and click Run SpecFlow Scenarios. You will then see the test will run, and if you open the Visual Studio Test Explorer, you will see your results. The example has some other tests in it to show a few other ways or writing scenarios.
The example can be downloaded from here: BDD Example Application