The Code Gorilla

Wednesday, 20 March 2013

Getting started with SpecFlow (Part 1)

This blog post will, hopefully, provide a good starting point for those interested in using SpecFlow and BDD. This example is developed using SpecFlow 1.9, MsTest and Visual Studio 2012.

I won't go into what BDD is, or try to explain the Gherkin language as used in Specflow. This will be a basic project run through to get you started with your first Specflow & BDD based projects.

This project will be based on developing a simple system for the purpose of booking appointments at a dentist, there a multiple layers to this n-tier system.

Data Layer (DataModel)
 |
Domain Layer (DomainModel)
|
Presentation Layer (UI)

Part 1 - Defining the Scenarios

In this example, we shall be using BDD to explain the behaviour of the business domain layer and not worry about the presentation layer for now (I wish to keep the example simple).


Download source code

Part 2 is here.

Start using SpecFlow

  1. From Visual Studio, go to tools -> Extensions and Updates and install the SpecFlow extension, you will then need to restart Visual Studio.
  2. Create a new Unit Test Project (MsTest), name the solution Dentist and the unit test Dentist.Specs.
  3. Delete the stub file UnitTest1.cs that is created for you.
  4. Right click on the Dentist.Specs project and select Manage NuGet Packages and install the SpecFlow package for this project.
  5. Edit the App.config file for the Dentist.Specs and add the line <unitTestProvider name="MsTest"/> within the <specFlow> section. This is an important step to make SpecFlow work with MsTest.

Once those steps are completed we are ready to begin. Note: With the exception of step 1 all the remaining steps will be required on all future SpecFlow test projects.

A Quick Introduction


The Gherkin language breaks a feature (written as a user story)  into scenarios to help better explain the features use cases, each scenario is defined by  Given state When event Then outcome syntax. The language to write the state, event and outcomes is very natural. States, events and outcomes are referred to as steps, these  steps can be combined using And, e.g.

    Given state1
    And state2
    When event1
    Then outcome1
    And outcome2
    And outcome3

In AAA terms (3A - Arrange Act Assert, for those from TDD), the state (Givens) steps represent Arrange, the events (Whens) represent Act and the outcomes (Thens) represent the Assert. Steps are reusable between scenarios and have a global scope within the SpecFlow project by default to allow step reuse, unless otherwise specified with the ScopeAttribute in the code for the feature class or step(s).

Our first feature


Lets start with our first feature, in normal use you would prefer the domain experts to write the features and scenarios but its likely they will need some assistance in the early days.

Right click on the Dentist.Specs project and Add -> New Item, select a new item of type SpecFlow Feature File, change the file name to BookAnAppointment.feature,and click Add. This creates a template of a feature file for us to use, it contains a sample feature and a sample scenario.

Delete the contents of the file and lets start with our new feature for booking an appointment.

Feature: Book an appointment
    In order to have my teeth fixed
    As a patient
    I need to be able to book an appointment to see my dentist

The feature name will form the class name for the scenario steps that will be created later. As with normal user stories, there is a user, what the user wants/desires and the benefit.

Our first scenario


Now lets define the first basic scenario that would play out here, this is added to the feature file we just created.

Scenario: Successfully booked the appointment
    Given the patient is already registered with the dentist
    And The appointment time I have chosen is available
    When I book my appointment with my dentist
    Then I get confirmation that the appointment is booked
    And that appointment time is no longer available to other patients

As you can see, that is a very readable scenario and is in natural language. We could assume that it's now complete but there are a few changes we can make to the scenario to make steps reusable, as the aim is to reuse the steps we generate if we can. In this example there are some questions that need to be answered such as what patient, what dentist, what appointment time and what appointments are currently booked.

Note: To generate the code, right click within the feature file and select Generate Step Definitions, this you can only do once for the whole file, new steps can be generated again but changes to existing steps are a little bit more interesting. (If you find or know of a better way then please let me know in the comments field below).

Now lets change this scenario to include some data as well, at this point we also note that we do not know how long an appointment lasts so we need to add a default.

Scenario: Successfully booked the appointment
    Given The patient is  already registered with the dentist
        | patients       | dentist     |
        | David Wyles | Dr Jones |
    And The dentists existing appointments are
       | dentist    | Appointments     | patient       |
       | Dr Jones | 13/9/2013 9:15 | Joe Bloggs   |
    And Appointment slots are 15 minutes
    When An appointment request is made
       | patient         | dentist   | appointment request |
       | David Wyles | Dr Jones | 13/9/2013 10:00       |
    Then There is a confirmation that the appointment is booked
    And The dentists appointments are
       | dentist    | Appointments     | patient          |
       | Dr Jones | 13/9/2013 9:15   | Joe Bloggs    |
       | Dr Jones | 13/9/2013 10:00 | David Wyles |

Notice how I have removed all first person terms like I from the Given, When and Then parts.

I've introduced a couple of new items in this one, all in italics. These items represents the data passed to the step that will be created, if you go ahead and create the steps now you will see a function for the last step as

[Then(@"the dentists appointments are")]
public void ThenTheDentistsAppointmentsAre(Table table)
{
    ScenarioContext.Current.Pending();
}

The table shown in the scenario is converted into a Table type that consists of rows, each row is a key value pair. The key is the column header, and the value the item in the row for that column. This represents one way to get data from your scenario into your step, thus allowing for greater reuse potential of that step. I  normally rename the parameter to make it more readable, something like 'appointments' in this case. Another example of a parameter is the statement "appointment slots are 15 minutes", the 15 is translated into an int parameter in the generated steps.


More scenarios


Before we go any further, lets get back to the feature file as their are more scenarios for us to define, the next one to look at is where an appointment request fails.

Scenario: Failed to book the appointment
    Given The patient is already registered with the dentist
        | patients        | dentist  |
        | David Wyles | Dr Jones |
    And The dentists existing appointments are
       | dentist     | Appointments    |
       | Dr Jones | 13/09/2013 9:15 |
    And Appointment slots are 15 minutes
    When An appointment request is made
       | patient          | dentist    | appointment     |
       | David Wyles | Dr Jones | 13/09/2013 9:15 |
    Then There is a rejection as that appointment slot is already booked
    And A list of alternate appointment slots for that day are given
       | dentist     | Available                           |
       | Dr Jones | 13/09/2013 9:30 to 13/09/2013 17:00 |

For now, we'll leave it at just two scenarios, there are more that can be easily deduced but I wish to keep this sample simple. You should be able to see that the two scenarios are identical for all steps up to checking the outcomes after the When event, remember that the data is passed as a parameter to the step so it is not embedded within the code.

Now we could leave it at this point and go and generate the steps from SpecFlow, but I haven't finished refactoring my scenarios yet, we can write this even better and get more tests into our two scenarios without increasing the step code complexity.

Scenario Outlines & Reuse


Now lets refactor these two scenarios to a better feature file and start to use Scenario Outlines and increase our scenarios test cases. These scenarios have very similar Givens, so we can create a Backgound to use in all our scenarios, this creates the given information for all our scenario outlines. We can also make use of Examples to run the scenarios will multiple data sets.

Background: 
Given The Dentists
| dentist   |
| Dr Jones  |
| Dr Skills |
And The Registered Patients
| patient     | dentist   |
| David Wyles | Dr Skills |
| Phill Fry   | Dr Jones  |
| Mike Knight | Dr Skills |
| Bill King   | Dr Jones  |
And The Current Appointments
| dentist   | appointment   | patient     |
| Dr Jones  | 13/9/13 9:00  | Phill Fry   |
  | Dr Skills | 13/9/13 11:00 | Mike Knight |
And Appointment slots are 15 minutes

Scenario Outline: Successfully booked the appointment for a registered patient
    When An appointment request '<appointment request>' is made for patient '<patient>'
    Then There is a confirmation that the appointment '<appointment request>' is booked for the patient '<patient>' with the dentist '<dentist>'
    And The appointment '<appointment request>' is not available for dentist '<dentist>'

    Examples:
        | patient     | dentist   | appointment request |
        | David Wyles | Dr Skills | 13/9/13 10:45       |
        | David Wyles | Dr Skills | 13/9/13 9:00        |
        | Bill King   | Dr Jones  | 13/9/13 9:15        |


Scenario Outline: Failed to book the appointment
    When An appointment request '<appointment request>' is made for patient '<patient>'
    Then The appointment request fails
    And Alternative appointments '<alternatives>' for dentist '<dentist>' for that day are given
 
    Examples:
        | patient     | dentist   | appointment request | alternatives                |
        | David Wyles | Dr Skills | 13/9/13 11:00       | 9:00 - 10:45, 11:15 - 16:45 |

I've introduced some new terms here here should be explained, there is the <> notation and the Examples table. The Examples table represents test cases for the scenario, each row being one test case. The <> notation refers to a column in the examples table. Each scenario is given the Background state information (Givens) before the outline is run.

We are now ready to generate the steps, in this feature file we are reusing most of the steps, as you develop more and more scenarios the steps will get more reuse over time. Using Examples allow for additional test cases to be added to the scenarios without you having to change the code in the steps.

The scenarios are also helping design both the data model and the domain model, which is the aim of BDD.

As a side note, observe how I use the data (the bit in <>) as a separate item. I could write my scenario as: Given The '<patient>' is registered with the '<dentist>' as that seems more natural but the code step that is generated by SpecFlow is:
void TheIsRegisteredWithThe( string p0, string p1 )
This isn't very useful for the person writing the step or reviewing it months later, as such I tend to write the line as: Given The patient '<patient>' is  already registered with the dentist '<dentist>',which is still readable and more importantly the Specflow generated step reads as:
void ThePatientIsRegisteredWithTheDentist( string p0, string p1 )
With a quick rename of the parameters we have
void ThePatientIsRegisteredWithTheDentist( string patient, string dentist )
At this point both the feature file and the code step is readable, and this is important when reading the  inevitable test failures reports.

That's it for part one, in part two we'll focus on writing the code for the steps.

2 comments:

  1. Take advantage of every opportunity to practice your communication skills so that when important occasions arise, you will have the gift, the style, the sharpness, the clarity, and the emotions to affect other people. See the link below for more info.


    #skills
    www.ufgop.org

    ReplyDelete
  2. Hi David, hope all is well, are you open to any Scala contract work at the moment?

    BR
    Sandeep.mangat@digitechresourcing.co.uk

    ReplyDelete