Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Enterprise Developer

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « Automatically Version Control Your Jenkins ConfigurationContinuous Delivery - Dashboard, QA and Production Deployment »
    comments

    SpecFlow is a .Net library that allows us to describe user expectations in a consistent Domain-Specific Language that can be wired for automatic execution. By writing executable tests in a human readable manner, our tests can serve as a bridge between the users expectations and the code we produce to meet them.

    This post will walk through the benefits and high level details of these methods before diving into a practical example of implementing several tests against the MVC Music Store site from my Continuous Delivery project. Along the way we will also use Selenium Web Driver, the Page Object pattern, and Nunit as we interact with an ASP.Net MVC site.


    ASP.Net MVC Music Store

    Why Executable Tests

    End users don't have a very clear idea of what they need. This is reflected in the requirements gathering time of projects that operate from detailed, up front specs. It's also reflected by Agile practices, which promote short, iterative coding phases, one purpose of which is to get quick user feedback specifically to mitigate this risk.

    Our users aren't to blame.

    Part of the problem is our misunderstanding of what the users are trying to achieve. With different backgrounds, vocabularies, and general contexts, communication gaps and misunderstandings are to be expected. Add the fact that our end user often has only a vague definition in their own mind of what they want, which takes seeing or using a particular piece of software to determine whether their idea is works or needs further improvement or refinement. And as if that weren't enough, there's often a failure ask for the appropriate level of understanding committing code to editor.

    Tests can't solve all of these issues, but they can help close the gap.

    SpecFlow, Gherkin, and Readable Tests

    SpecFlow defines features (user stories) and scenarios in a language called Gherkin, which describes itself as a Business-Readable Domain-Specific Language. Using a structured language to define business expectations first requires us do the appropriate level of analysis, or at least ask ourselves what the appropriate level of analysis is. Using a business-readable syntax means we can share the written requirements with our end user and they can confirm (or help correct) our understanding of their expectations.

    With our user expectations written in a clearly structured DSL, we can then use a parser to wire those expectations directly to test code, minimizing any further misunderstandings or translation errors that would occur if we were write up the executable tests separately. Now we can consistently test our application against the very clear expectations we have confirmed with the end user.

    Following the Behavior Driven Development (BDD) or Acceptance Test Driven Development methods, if we wire these tests up prior to starting to code for them, then we can help keep our work targeted directly to the end user expectations, minimizing waste from unnecessary additions and catching potential problems far sooner.

    Introducing the project

    As part of the Continuous Delivery project I mentioned above, I implemented a barebones interface testing project that uses Selenium WebDriver and Nunit to execute a couple basic tests against a deployed MVC site. The raw source code for the project is available on BitBucket.

    If you haven't used Selenium before or are unfamiliar with the Page Object pattern, you can find more information in this earlier post which covers these topics in depth.


    MVCMusicStore Interface Tests Project

    Currently the project has a minimal base page object and a few lightly defined pages, just enough to support a test to browse the main navigation links through the site and verify some genre links work. The SpecFlow tests that I add to the project will force me to continue fleshing out the necessary functionality of the Page Library and test project as we need new functionality or shortcut methods to keep the test implementations clean and readable.

    The tests are written in Nunit and extend a TestFixtureBase class that is responsible for initializing a RemoteWebDriver (browser) and quitting it for each individual test. A singleton Settings object exists to load settings from a local configuration file for the full test run, letting us provide a configured base URL and to later add sample values that are specific to the environments the tests run in.

    Adding SpecFlow

    The first step is to add SpecFlow. While there is a NuGet package for it, you will actually want to download the SpecFlow installer and install it. The installer includes templates, intellisense, and other bits and bobs that you won't get with the NuGet package. That said, I actually did both, first installing it then using NuGet to pull down the package for my project so I could commit the packaged references to the projects source code repository.

    After installing the full install package, there will be a few new items in the "Add New Items" menu in Visual Studio.


    "Add New Item" dialog in Visual Studio

    The "SpecFlow Feature File" option will create a new pair of files for our feature and the generated code behind that feature. The feature is where we will write our user story and the individual scenarios in Gherkin. The generated code reflects the scenario as an Nunit test, with each step in the scenario treated as a potential function to call out to.

    The "SpecFlow Step Definition" item is just a *.cs class file that we would put the individual step functions in for the tests to call. Both the Feature File and Step Definition file are populated with an addition sample as part of their template..

    Adding a SpecFlow feature

    To start with, lets discuss what we're going to be testing. Since the application is a Music store, lets define how we want the cart total to work. Here's the User Story (feature) we will be working with:

    As a shopper I would like to see my up to date cart total as part of each screen so I don't have to visit my cart to verify my items are in it

    Now this probably sounds like enough to go ahead an implement it, but lets nail down some scenarios first. If I was an working with an end user, these are the type of things we would probably come up with:

    • When I have nothing in my cart, it displays a total of 0
    • When I add an item to my cart, it displays a total of 1
    • When I add two of the same item to my cart, it displays a total of 2
    • When I add two separate items to my cart, it displays a total of 2
    • When I have two items in my cart and I remove one, it displays a total of 1
    • When I have two items in my cart, after I checkout it displays a total of 0

    Note that these are a collaborative effort. My pretend end user probably came up with some, which I then helped to expand on via questions and experience.

    Writing this in a Feature File in Gherkin we would have something like:

    1. Feature: Cart Total
    2.     As a shopper
    3.     I want to see my cart total on every screen
    4.     So I don't have to leave my current page to verify it's contents
    5.  
    6. @UI
    7. Scenario: Empty Cart
    8.     Given I have the Home Page open
    9.     Then the cart is empty
    10.  
    11. @UI
    12. Scenario: Add an Item
    13.     Given I have the Home Page open
    14.     And I select a genre from the left
    15.     And I select an album from the genre page
    16.     When I add the album to my cart
    17.     Then the cart has a total of 1
    18.  
    19. ...

    I've added a SpecFlow Feature File to the project and I can write all of the scenarios for the feature. After saving it, I can run the Nunit GUI and see a series of new, inconclusive tests, each named after the Scenario name I provided in the Feature File.


    Inconclusive tests in Nunit GUI

    Feature File: View the file on BitBucket

    Now it's time to start wiring them into the Page Library calls.

    Wiring in the Browser

    When each scenario runs, we want it to start clean with a fresh browser instance. This is similar to how the existing TestFixtureBase works, so we can reuse that object with a few tweaks. The challenge with the SpecFlow scenarios is that I will be reusing steps in several different features and I don't want several different browsers to open based on which class is instantiated to make a coded step definition available. Also, unlike the current interface tests that execute inside a single method, the fact that we are running tests across multiple methods will make it trickier to keep track of the current page instance.

    Based on coding up several earlier SpecFlow steps, I ended up with a base class for my step definitions that helped resolve both of these issues, while also providing some properties to help improve readability.

    InterfaceTests/Features/FeatureBase.cs

    1. namespace MvcMusicStore.InterfaceTests.Features {
    2.         public class FeatureBase : TestFixtureBase {
    3.  
    4.                 #region Properties for Readability
    5.  
    6.                 /// <summary>
    7.                 /// Shortcut property to Settings.CurrentSettings.Defaults for readability
    8.                 /// </summary>
    9.                 protected DefaultValues Default { get { return Settings.CurrentSettings.Defaults; } }
    10.  
    11.                 /// <summary>
    12.                 /// Sets the Current page to the specified value - provided to help readability
    13.                 /// </summary>
    14.                 protected PageBase NextPage { set { CurrentPage = value; } }
    15.  
    16.                 #endregion
    17.  
    18.                 protected PageBase CurrentPage {
    19.                         get { return (PageBase)ScenarioContext.Current["CurrentPage"]; }
    20.                         set { ScenarioContext.Current["CurrentPage"] = value; }
    21.                 }
    22.  
    23.                 [BeforeScenario("UI")]
    24.                 public void BeforeScenario() {
    25.                         if (!ScenarioContext.Current.ContainsKey("CurrentDriver")) {
    26.                                 Test_Setup();
    27.                                 ScenarioContext.Current.Add("CurrentDriver", CurrentDriver);
    28.                         }
    29.                         else {
    30.                                 CurrentDriver = (RemoteWebDriver)ScenarioContext.Current["CurrentDriver"];
    31.                         }
    32.                 }
    33.  
    34.                 [AfterScenario("UI")]
    35.                 public void AfterScenario() {
    36.                         if (ScenarioContext.Current.ContainsKey("CurrentDriver")) {
    37.                                 Test_Teardown();
    38.                                 ScenarioContext.Current.Remove("CurrentDriver");
    39.                         }
    40.                         string s = "";
    41.                 }
    42.         }
    43.  
    44. }

    I've used the SpecFlow hooks for BeforeScenario and AfterScenario to handle initialization and I've used the provided ScenarioContext to help store a common driver and the current page. I've also specified that these hooks only occur for tests tagged with "UI" so I can later create some additional tests that will make direct calls to the JSON controller endpoints without spinning up a whole browser session.

    At this point I still get all "Inconclusive" test results from Nunit, but I can see that each tests fires up a browser as Nunit progresses through the test run and the Before/AfterScenario hooks are called.

    Defining the Step Definition File

    With the Feature completed and a base Feature file created to handle our browser, it's time to write the code that will be executed behind the individuals steps of the file.

    Besides creating tests that result in "Inconclusive" results, SpecFlow also provides us with some basic code to get started with the step definition file. In the text output tab of the Nunit GUI we can see that each undefined step from SpecFlow outputs the information needed to implement the step, like so:


    Step Definition text in Nunit Text Output

    Back in Visual Studio I will create a new "SpecFlow Step Definition" file and copy the content of the Nunit Text output window into the class in this file, removing the unnecessary addition example steps and all the extra class definitions and plain text. Each statement that appears in a Scenario in the Feature File has a corresponding method in the generated Nunit test, so each one will need a method. I've actually cheated a little and reused some steps from some earlier SpecFlow features, so my final file only has the new steps defined:

    The other steps are in a previously defined Step Definition file. I'll probably centralize common steps at some point to make them easy to find, but for the meantime the base class will help keep the current page and web browser accessible to the steps in both files and I have few enough tests that I'll remember where those definitions are (remind me I said this when I go back in a week and make a fuss on twitter about not remembering where they are).

    Once I have the Step Definition methods setup, I can go ahead and wire in the code necessary to drive the browser. I'll walk through the methods for the "Add an Item" feature.

    InterfaceTests/Features/Cart.feature

    1. Given I have the Home Page open
    2.     And I select a genre from the left
    3.     And I select an album from the genre page
    4.     When I add the album to my cart
    5.     Then the cart has a total of 1

    Keep in mind if you look at the code repository some of these steps are in separate files.

    Given I have the Home Page Open

    Each of my scenarios starts with the same step, ensuring we have the browser open and pointing to the site. The step definition is then fairly straightforward, given the base class already ensures we have a fresh browser available:

    InterfaceTests/Features/NavigationSteps.cs - this is the step I borrowed from a previous test

    1. [Given(@"I have the Home Page open")]
    2. public void IHaveTheHomePageOpen() {
    3.     NextPage = PageBase.LoadIndexPage(CurrentDriver, Settings.CurrentSettings.BaseUrl);
    4. }

    The class definition for FeatureBase above includes a CurrentPage property that we use to store and retrieve the page object associated with the browsers current page. To improve readability a little, I've created the NextPage property, which is simply a setter for the CurrentPage one.

    And I select a genre from the left

    All pages in the application display the genre list on the left, so this makes a simple way to get to a specific genre page no matter where we are in the site.

    InterfaceTests/Features/CartSteps.cs

    1. [Given(@"I select a genre from the left")]
    2. public void GivenISelectAGenreFromTheLeft() {
    3.     NextPage = CurrentPage.SelectGenre(Default.Genre.Name);
    4. }

    All page objects extend the PageBase class, so I've added a partial class for PageBase (PageLibrary\PageBase.Navigation.cs) that includes navigation and behavior that's common to all pages in the application.

    Default.Genre.Name: As I mentioned earlier, there is a singleton Settings object that is responsible for loading settings from an XML file and making them available to the tests. I've added a Genre and Album element to the settings file so I can provide some default values without hardcoding them into the test code or, worse, each individual test. I then created another shortcut property in my FeatureBase so I can reference these values by the property name Default instead of the much longer Settings.CurrentSettings.Default.

    And I select an album from the genre page

    Once I have the genre page open, I can select an album that I intend to add to the cart.

    InterfaceTests/Features/CartSteps.cs

    1. [Given(@"I select an album from the genre page")]
    2. public void GivenISelectAnAlbumFromTheGenrePage() {
    3.     NextPage = CurrentPage.As<BrowsePage>().SelectAlbum(Default.Album.Name);
    4. }

    One of the downsides of having a single property to track the current page is that it is typed as a PageBase object. I could add cast statements to each line, but by adding a generic method to handle the cast, I preserve the left-to-right reading order of the statement. Had I used an inline cast, we would be looking at:

    1. [Given(@"I select an album from the genre page")]
    2. public void GivenISelectAnAlbumFromTheGenrePage() {
    3.     NextPage = ((BrowsePage)CurrentPage).SelectAlbum(Default.Album.Name);
    4. }

    Which just doesn't seem as readable to me.

    I've added the generic cast method to the PageBase method to make it easily accessible:

    PageLibrary/Base/PageBase.cs

    1. public abstract partial class PageBase : CommonBase {
    2.  
    3.     //...
    4.    
    5.     public TPage As<TPage>() where TPage : PageBase, new() {
    6.         return (TPage)this;
    7.     }
    8. }

    When I add the album to my cart

    If you remember, the original scenario we listed above was "When I add an item to my cart, it displays a total of 1". Often it is fairly easy to separate the Given portion of our scenario from the When/Then portion because the Given part is often the part that we took for granted when we were describing the scenario or when it was described to us.

    InterfaceTests/Features/CartSteps.cs

    1. [When(@"I add the album to my cart")]
    2. public void WhenIAddTheAlbumToMyCart() {
    3.     NextPage = CurrentPage.As<AlbumDetailPage>().AddToCart();
    4. }

    As you can tell by now, the actual logic that goes into the step definition files is fairly minimal. This is by design and is similar to the MVC concept of a thin controller. By keeping the page behavior in the page objects, we're attempting to minimize the brittleness of our test code.

    Then the cart has a total of 1

    The last step is to verify the expectation. We're going to do something a little special with this step because it matches a similar step across several of the tests, with the exception of the number we are expecting to see.

    InterfaceTests/Features/CartSteps.cs

    1. [Then(@"the cart has a total of (\d+)")]
    2. public void ThenTheCartHasATotalOf(int expectedTotal) {
    3.     CurrentPage.VerifyCartTotalIs(expectedTotal);
    4. }

    SpecFlow allows us to enter a regular expression in the step definition, which it will then use to populate arguments for our step definition function. So instead of making a separate function for testing a cart total of 0, 1, and 2, I can make one function that tests whichever value matches the match group in my decorators expression.

    Result

    With all of the steps built, I can now run the test for verification:


    Pass test run for 'Add Item' scenario

    The text output tab of Nunit still provides us with information at the step level, but more importantly we now have a "Pass".

    Wrapping Up

    By capturing the end users expectations in this way, we have some structure that helps gather them at a good level of detail while also providing a testable version that we can automatically run as we develop the solution and as a regression suite when we are finished. The requirements are readable by our end user, by ourselves, and can be programmed against. As we build up a library of common step definitions we will start being able to put new tests together even faster as well as have some visibility into what portions of the application are the most critical (if a step shows up in 50% of our tests, it's a good bet it's a lot more critical to the stability of our application than the item that shows up once in a single test).

    All of the code for this project is available in BitBucket.

    About the Author

    User bio imageEli delivers software and technology solutions for a living. His roles have included lone developer, accidental DBA, team lead, and even unintentional Solaris consultant once. With experience in adhoc, Lean, and Agile environments across NSF grants, SaaS products, and in-house IT groups, he is just as willing to chat about the principles of Lean or Continuous Delivery as he is to dive into Azure, SQL Server, or the last ATDD project he created.
    Social SitingsTwitterLinkedInHomePagedeliciousLTD RSS Feed
    Instapaper

    6 comments

    Comment from: Marcus Hammarberg [Visitor] · http://www.marcusoft.net
    Marcus Hammarberg Nice post!

    I really liked the step-by-step description. Very well written and easy to follow through the whole post.

    Love your implementation of the PageObject pattern too, makes the code so much readable.

    I had some problems finding the steps in the source code thought. Just a convention that you might consider; many people put the step classes in separate folders (called .. wait for it ... Steps).
    That'll leave your features and the generated in one place and the steps code that you write in a separate place.

    Thanks for a great post.
    01/30/12 @ 07:12
    Comment from: Eli Weinstock-Herman (tarwn) [Member]
    Eli Weinstock-Herman (tarwn) Thanks Marcus. I'll probably take you up on the suggestion to split the steps into a separate folder.

    I've been also starting to do more research lately into how other people have managed the organization of individual step definition methods. I currently have them organized based on the Feature file they were added to support, but I could see also grouping them into step definition files by the page they interact with, or for the more complex ones, maybe some other form of workflow categorization.
    01/30/12 @ 09:42
    Comment from: Ben Paul [Visitor] Email
    Ben Paul Wonderful article, this has helped me immensely.

    Would love your input on the journey I've taken so far, getting started with C# UI testing. What gaps do you see that I can improve on? Were any steps unnecessary?

    1. Make scripts with Selenium IDE to test a website
    2. Export scripts to C#/Selenium RC and get them to work with NUnit
    3. Add parameterized NUnit tests to handle multiple user types signing into the website
    4. Refactor tests to fit into SpecFlow framework
    5. ???

    Thanks so much for your guidance!
    Ben
    05/07/12 @ 12:04
    Comment from: Eli Weinstock-Herman (tarwn) [Member]
    Eli Weinstock-Herman (tarwn) @Ben: I think the next step is to take the framework you have in place with SpecFlow for your application and start building tests for new features you haven't implemented yet. So instead of testing what is already there, build tests that define the behavior you are expecting to build in, then build just enough interface and code to make those tests pass. This is BDD (Behavior driven development), a subset of TDD.

    Another option would be to try recreating the same tests in a Ruby, Cucumber, Watir stack using the same page object pattern that I outlined above. Switching stacks to work on a similar theoretical level can provide some excellent contrast and potentially uncover some things with C# you wouldn't have otherwise tried. A good set of posts on Page Objects from a Ruby perspective starts here: http://www.cheezyworld.com/2010/11/09/ui-tests-not-brittle/
    05/07/12 @ 16:48
    Comment from: Markus Mayer [Visitor]
    Markus Mayer Hi Eli,

    great work! Helped me a great deal to jump-start our UI-Testing framework.

    Best regards,
    Markus

    PS: Do you happen to know why the BeforeScenario()-Methode "fires" multiple-times per Scenario? To prevent Firefox to open multiple-times you added this code that looks to me like a workaround. Or is it a feature ;-) ?
    05/08/12 @ 02:36
    Comment from: Eli Weinstock-Herman (tarwn) [Member]
    Eli Weinstock-Herman (tarwn) @Markus: Thanks, I'm glad you found it useful. My BeforeScenario and AfterScenario calls are defined in the base class and my step files inherited from that base class, so this looks like multiple BeforeScenario definitions to SpecFlow. SpecFlow treats these hooks are global, so in most situations you would use tag filtering to limit which hooks it calls. Given the inheritance model I was using would still end up having the same hooks called multiple times, I added the workaround to artificially limit it to one browser and so on.

    There's more information on the hooks and using tags for filtering here: https://github.com/techtalk/SpecFlow/wiki/Hooks
    05/08/12 @ 04:19

    Leave a comment


    Your email address will not be revealed on this site.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)