Converting Business Requirements Into Gherkin
In the previous post, I very briefly talked about the Gherkin syntax. I also referenced a site that goes into a little bit more detail. I now want to discuss the syntax in how it specifically relates to our simple calculator project. From the previous page, you’ll remember we currently have 5 main business requirements (the first requirement is really just describing what type of application we’re developing). Based on the requirements, we may want to write some of the following tests:
- Ensure there are two textboxes on the page
- Ensure there is a dropdown on the page
- Ensure there is a button that, when clicked, calculates the value
- Ensure that, depending on role, the correct operators are available
- Ensure that the right calculation is produced
Each one of these tests are what we would consider a scenario. In other words, given the correct pre-conditions, when an action takes place, then a certain result(ing action) takes place. Now, looking at converting these to backlog items maybe too nitty-gritty – too low-level – as, more than likely, you’re not going to have a single backlog item requesting to add two textboxes to the page. In reality, this would probably be a task under a backlog item. As you add functionality to your calculator (e.g. scientific notation), you then may have a new backlog item to incorporate the feature, but your acceptance test(s) would simply be updated to meet the new requirement(s). Again, your acceptance tests are your living document – they live, breathe, and adapt to your application over time.
In light of all of this, let’s simplify the above tests. And, this time, I’ll use Gherkin to describe our scenarios.
Scenario: Check for required page elements
Given the application is loaded
When the user is on the "Calculator" page
Then the page contains a "input" element with id "num1"
And the page contains a "input" element with id "num2"
And the page contains a "select" element with id "operator"
And the page contains a "button" element with id "calc"
And the page contains a "div" element with id "result"
In this first scenario, notice what I’ve done. I’ve used one scenario to cover three business requirements. Furthermore, I can now take this scenario and write a User Story for my backlog like so:
As a user, I want to enter two numbers and select an operator so that when I click a button, the result is shown.
With this user story and the accompanying acceptance test, I am a better informed developer. I now have a clear picture of what the business envisions and expects. Otherwise, who knows, I could have ended up with buttons 0-9, operators and a decimal (like our keyboard). Without the acceptance test, I would not have fully comprehended the business’s expectations and time spent in development would have been wasted.
Let’s write a couple of more acceptance tests.
Scenario: Check for simple operators
Given the user role is simple
When the user is on the "Calculator" page
Then the available operators should be "+,-"
Scenario: Check for advance operators
Given the user role is advance
When the user is on the "Calculator" page
Then the available operators should be "+,-,*,/"
Where the first acceptance test checked for functionality, these two acceptance tests check for authorization. It is important when writing acceptance tests that you write positive and negative tests (e.g. the simple user has operators “+,-“; and, the simple user does not have operators “*,/”). This way, you can always ensure the security of your application while the code may continue to change.
Finally, let’s write an acceptance test for data.
Scenario Outline: Check calculations
Given the user role is <role>
When the "input" element with id "num1" contains <value1>
And the "input" element with id "num2" contains <value2>
And the "select" element with id "operator" contains "<operator>"
And the user clicks the "button" element with the id "calc"
Then the "div" element with id "result" should contain <result>
| role | value1 | value2 | operator | result |
| simple | 5 | 4 | + | 9 |
| simple | 5 | 4 | - | 1 |
| advance | 5 | 4 | * | 20 |
| advance | 5 | 4 | / | 1.25 |
Instead of writing multiple scenarios with varying data, we can write one scenario outline that serves as a template. Then, using the examples table and merge fields, I can run the given scenario 4 different times while substituting new values each time.
Adding Our Scenarios to Visual Studio
We are now ready to add our scenarios to our acceptance tests project. We’ll begin with a good directory structure because we all appreciate quality information architecture. Right?
While applications like MVC are very conventional in their setup, there is no such formal directory convention in SpecFlow. However, I typically setup my directory structure according to purpose like so:
/Features // Contains all of my SpecFlow feature files
/SharedSteps // Common steps that are used across all features (e.g. authentication, page interaction, site navigation, etc.)
/StepHelpers // System steps (e.g. opening the web browser, setting up the environment for DryRunner, etc.)
/Steps // Contains all of my step class files
You may not know what Feature and Step files are yet, but that’s okay – we’ll get to it. Keep in mind that SpecFlow doesn’t care how you organize your code as long as it can find the features and their accompanying steps. But, as stated above, the directory structure above facilitates the separation of my code according to the purpose for easier management.
So let’s begin by adding our directories.
Now, we’ll need to add our Features. Because we’ve installed the SpecFlow Visual Studio extension, we have the ability to create a file of type SpecFlow Feature that ends with a .feature extension. Features contain all of our business requirements (i.e. user stories) under test and their accompanying scenarios. Since our application is only performing a single calculation, we’ll name our feature “Calculation.feature”. So, let’s create the “Calculation.feature” file in our Features folder:
- Right-click the Features folder and choose Add->New Item from the context menu
- Choose SpecFlow Feature File (NOTE: You must have the SpecFlow Visual Studio extension installed to see this item type.)
- Name the file Calculation.feature
- Click Add
Once you add the folders and the feature, your solution explorer should look like the following.
You’ll notice that the Calculation.feature file has some contents in it. It has a Feature section and a Scenario section. Keep in mind that the feature is typically considered an Epic in Agile development and each scenario is considered a product backlog item. This application, however, doesn’t warrant such depth.
It just so happens that the default content is based on writing a calculator application. Don’t get your hopes up, SpecFlow doesn’t have any clue what type of application you’re writing. This is just the default template. But, you can see that the feature description looks almost like our user story. You can place anything you want here to describe the feature. If you’d like to stick with the Cucumber format for describing features, go for it. However, because I work with teams that are implementing Agile, I like to keep things consistent. So, I’ll typically use the Agile User Story format when describing my feature – but, I’ll still separate the user story to three lines for clarity. So, let’s replace the description with our user story from above and then add our four scenarios. Once we’re done, our Calculation.feature should look like the following.
We now need to create steps to test scenarios. Each line in the above scenarios is consider a step and, thus, should have matching code to test that step. Cucumber matches each step using regular expressions. Typically, Cucumber could give us a few possible outcomes. First, if the step has no corresponding code, as is the current case, then Cucumber would tell us that the scenario is not fully implemented. If the step does have code, but the step fails, then Cucumber would report that the scenario fails. In Cucumber, a non-implemented scenario isn’t technically a failed scenario. Technically, its an incomplete scenario and Cucumber tells us as such when the scenario has attempted to run. A scenario only fails when each step has been implemented with code and a step fails. Something to note…In either case, once Cucumber experiences a problem with the scenario, the remaining portion of the scenario is skipped. In Visual Studio (MSTest), if a step definition cannot be found, like Cucumber, SpecFlow will skip the test.
As is typical with test-driven development, no code in our application has been written yet. We’re still working on business requirements. As I explained in the previous post, we create our acceptance tests first, then our unit tests for each acceptance test knowing all along that all tests will fail until actual code is implemented. When code is added, we should begin to see our unit tests pass and, eventually, our acceptance tests – once all unit tests of the corresponding acceptance test have passed.
As you examine the scenarios, you’ll notice a lot of repetition. Some of the repetitive steps have to do with generic page interaction and DOM elements while others are specific to the calculation, itself. You’ll also notice that in these repetitive steps, they only differ by what’s contained in double quotes. (Are you picking up on what I’m doing yet? Think…regular expressions.) The light bulbs should begin to flicker on.
Let’s create some steps. I’m not going to cover all steps here as you can download the source and view all the steps. I really just want you to get an idea of what’s going on.
First, I’ve added some additional files to my project allowing me to better categorize their functionality.
/Calculation.feature // My feature file that contains my scenarios (acceptance tests)
/Authentication.cs // Responsible for switching between my two roles
/PageInteraction.cs // Generic (shared) steps for interacting with pages
/SiteNavigation.cs // Steps for navigating through pages (e.g. loading a specific page, clicking on menus, etc.)
/WebBrowser.cs // The setup and tear down fixtures that execute (and dispose of) DryRunner
/Calculation.cs // Steps specific to my 'Calculation' feature
As I’ve just stated, the WebBrowser.cs class file is our key to executing DryRunner. If we weren’t using DryRunner, a couple of methods in this file wouldn’t be needed (specifically, those involving IIS Express). However, the class file has test setup and tear down fixtures that instantiate IIS Express on the specified port, then builds and deploys our web application to that IIS Express instance. In the tear down, the IIS Express process is stopped. This file also gives us a static instance of our browser (in this case, IE). There’s a third fixture that runs at the end of each scenario. By default, the browser is closed and garbage collected upon the completion of each scenario. But, if a scenario throws an exception, WatiN’s exceptions do NOT bubble up and, therefore, the browser will remain open with the test failing. Of course, we don’t want any orphaned instances of the browser, so we’ll ensure that the browser is, in fact, closed and disposed of by using the AfterScenario fixture. Again, WatiN currently supports IE and Firefox.
NOTE: The port for DryRunner does NOT have to been the same auto-generated port configured in the debug settings of your web application. And, typically, you wouldn’t want it to be as this allows you to debug and execute acceptance tests on different IIS Express processes. However, this should be the same port that you have configured for your debug appSetting(s) value in your app.config that we set previously.
The WebBrowser.cs looks like the following:
A partial of our Calculation.cs looks like the following (download the solution to see the full file):
The image shows three acceptance test steps (methods). A few things to note…
- You’ll notice that the steps are annotated with either Given, When, or Then followed by a regular expression. As stated previously, our features’ steps are matched to methods using regular expressions.
- We use regular expression grouping to extract and pass parameters to our methods.
- The difference between the second and third method is that the final parameter of the second method is a decimal, while the final parameter of the third method is a string. Notice, however, the regular expressions. The only difference in the regular expressions is that the latter includes double quotes around the final parameter.IMPORTANT: SpecFlow treats ALL regular expression groups as strings. It is up to your method definition to covert the groups to parameters of the proper type. Therefore, we use the escaped double-quotes in the second regular expression to denote the calling of our third method accepting a string as the final parameter.
- There are no assertions to test whether the element selectors (lines 24, 44, 59) return an element or if they return NULL. If the element cannot be found, WatiN returns its own WatiN.Core.Exceptions.ElementNotFoundException and the test fails (this is why we created the AfterScenario fixture above). This, initially, is what we’d expect due to the fact the application has yet to be built (or, the feature has yet to be implemented). Only if the element is found will the test continue and we can run our assertions.
- The method names should be unique. If, in our Visual Studio Test Explorer, we wish to group our tests by Traits, then the acceptance tests (i.e. scenarios) will be grouped by Feature allowing us to quickly troubleshoot our failing tests. Additionally, each unique permutation of our scenario outline is grouped.
- We can also perform page interaction with form elements such as adding text to text fields (line 45), selecting values in select lists (line 60), and clicking on links/buttons (see PageInteraction.cs line 36).
- My test assertions are performed before the page interaction, otherwise, I may lose focus of the page and the assertions can fail. Also, if an assertion does fail due to something like a button doesn’t exist, I obviously want my test to give me a descriptive reason in Test Explorer on why it failed instead of the click operation on the button giving me some cryptic message.
Now that I have acceptance tests for my Epics/User Stories, I generally would begin writing unit tests for my tasks. (I’m not going to write any unit tests for this project as it’s outside of scope for this article.) When all said and done, of course, all tests – acceptance and unit – would fail because the application has not yet been written. But, as stated many times already, as I begin writing my application, I should start seeing my unit tests and, eventually, my acceptance tests pass.