Kash Farooq's software development blog

.NET Developer

Posts Tagged ‘BDD’

BDD with SpecFlow and Coypu (Part 2)

Posted by Kash Farooq on August 23, 2011

In my last post I introduced BDD with SpecFlow. I created a  feature file and got to a point where I now needed to hook up my BDD tests with browser integration. To do this, I’m going to use Coypu and the Selenium headless browser. See my previous post for download links.

First, let’s configure Coypu to use the Selenium headless browser:

[Binding]
public class CoypuInitializer {
    [BeforeTestRun]
    public static void BeforeTestRun()
    {
        //I'm using the Dev Studio cassini server;
        Configuration.AppHost = "localhost";
        Configuration.Port = 64567;

        ConfigureForHeadlessBrowser();
    }

    private static void ConfigureForHeadlessBrowser()
    {
        Configuration.Driver = typeof(SeleniumHtmlUnitWebDriver);
    }

    [AfterScenario]
    public static void AfterScenario()
    {
        Coypu.Browser.EndSession();
    }
}

 

public class SeleniumHtmlUnitWebDriver : SeleniumWebDriver {
    public SeleniumHtmlUnitWebDriver() : base(new RemoteWebDriver(DesiredCapabilities.HtmlUnit())) {}
}

You will also need to add DLL references to Coypu.dll and WebDriver.dll (which is in the Coypu zip).

You can use this class to make other global configuration settings such as the Timeout, or the time Coypu waits between retries when looking for a HTML element. Full details can be found at the GitHub Coypu website, or the README.md file that comes with the Coypu binaries.

Now we are ready to implement the first feature step. As a reminder, the feature file is:

Scenario: Display Pi using Taylor series and Bucknall's BigNumber to many decimal places
	Given I visit the Pi.NET website
	And I have selected the 'Bucknall Big Number' algorithm
	And I have entered 500 decimal places
	When I press Go
	Then Pi should be displayed to the correct number of decimal places
	And Calculation statistics should be displayed

Let’s implement the “Given I visit the Pi.NET website” step:

[Given(@"I visit the Pi\.NET website")]
public void GivenIVisitThePiWebsite() {
    Browser.Session.Visit(string.Format("/"));
}

Using Coypu, I’m going to open the website specified in the CoypuInitializer class and visit the root of that website.

I now go to my MVC app and implement just enough to get that step passing.

Once I’ve done that, running the tests now gives:


Given I visit the Pi.NET website
-> done: PiNetSteps.GivenIVisitThePiWebsite() (15.4s)
And I have selected the 'Bucknall Big Number' algorithm
-> pending: PiNetSteps.GivenIHaveSelectedTheBucknallBigNumberAlgorithm("Bucknall Big Number")

i.e. – the first step has completed and the test has stopped at the next pending step. We’ve successfully hit the website.

I proceed like this, step by step, until I end up with the following steps file. I’ve added comments to explain the Coypu features I am using:

[Binding]
public class PiNetSteps:Steps {
    private string numberOfDecimalPlaces;

    [Given(@"I have selected the '(.+)' algorithm")]
    public void GivenIHaveSelectedAnAlgorithm(string algorithm) {
        //pass on this call to the generic radio button method (later in this class)
        Given(string.Format("I select the radio button '{0}'", algorithm));
    }

    [Given(@"I have entered (.+) decimal places")]
    public void GivenIHaveEnteredTheNumberOfRequiredDecimalPlaces(string numberOfDecimalPlaces) {
        this.numberOfDecimalPlaces = numberOfDecimalPlaces; //store for later use in an assert
        //populate a text box
        Browser.Session.FillIn("NumberOfDecimalPlaces").With(numberOfDecimalPlaces);
    }

    [When(@"I press Go")]
    public void WhenIPressGo() {
        //click a link. This fires an AJAX post to controller action
        //(I wanted to test JS capabilities of Selenium headless browser)
        Browser.Session.ClickLink("Calculate");
    }

    [Then(@"Pi should be displayed to the correct number of decimal places")]
    public void ThenPiShouldBeDisplayedToTheRequestedNumberOfDecimalPlaces() {
        //Check page HasContent I'm after - "Number of iterations" is displayed by AJAX call result
        if (Browser.Session.HasContent("Number of iterations:")) {
            //check Pi is calculated to correct number of places
            string piToNumberOfRequiredDecimalPlaces = Pi.Get(Convert.ToInt32(numberOfDecimalPlaces));
            Element piElement = Browser.Session.FindField("pi"); //find HTML element
            string calculatedPi = piElement.Text;
            Assert.That(piToNumberOfRequiredDecimalPlaces, Is.EqualTo(calculatedPi));
        }
        else {
            Assert.Fail("Could not find PI");
        }
    }

    [Then(@"Calculation statistics should be displayed")]
    public void ThenCalculationStatisticsShouldBeDisplayed() {
        //you can use actions/funcs with different retry timeouts
        Assert.IsTrue(Browser.Session.WithIndividualTimeout(TimeSpan.FromMilliseconds(1),
                       () => Browser.Session.HasContent("Number of iterations: ")),
                       "Number of Iterations taken not found");
        Assert.IsTrue(Browser.Session.WithIndividualTimeout(TimeSpan.FromMilliseconds(1),
                       () => Browser.Session.HasContent("Number of decimal places: " + numberOfDecimalPlaces)),
                       "Number of Decimal places calculated not found");
        Assert.IsTrue(Browser.Session.WithIndividualTimeout(TimeSpan.FromMilliseconds(1),
                       () => Browser.Session.HasContent("Elapsed Milliseconds: ")),
                       "Time taken not found");
    }

    [Given(@"I select the radio button '(.+)'")]
    public void SelectARadioButton(string radioButtonValue) {
        //select a radio button by value
        Browser.Session.Choose(radioButtonValue);
    }

    [Given(@"I visit the Pi.NET website")]
    public void GivenIVisitThePiPage() {
        //visit a web page at site specified in Coypu initialization.
        Browser.Session.Visit(string.Format("/"));
    }
}

I’ve used several SpecFlow and Coypu features in the class above. There are many more that you can read about at the GitHub Coypu website, or the README.md file that comes with the Coypu binaries. The documentation also shows how to use a real browser, rather than the Selenium headless one.

Posted in .NET, Behaviour Driven Development, Test Driven Development | Tagged: , , , , | Leave a Comment »

BDD with SpecFlow and Coypu (Part 1)

Posted by Kash Farooq on August 19, 2011

I’ve been doing TDD for years and I thought it was about time I got into BDD too.

I’m going to use BDD to add a UI in front of the various implementations of Pi calculation algorithms I’ve been working on.

This post will describe what you need to install and how to get a SpecFlow feature file running via NUnit.

Prerequisites

Headless browser:

Firing up a real browser from a BDD test can be slow. So, I’m using the Selenium headless Java browser.

Download selenium-server-standalone-2.4.0.jar (and, of course, you need Java)

To start it up:


java -jar selenium-server-standalone-2.3.0.jar

SpecFlow:

Install SpecFlow with the full installer and it will integrate with Dev Studio.

You can keep your feature files in your solution and SpecFlow will automatically generate NUnit based code-behind files.

I also recommend you watch the SpecFlow Screen Cast – an excellent introduction to quickly get you up and running.

Coypu

To get my SpecFlow BDD tests to interact with the browser, I’m using Coypu.

Coypu is:

A more intuitive DSL for interacting with the browser in the way a human being would, inspired by the ruby framework Capybara.

Other software

You need NUnit (as SpecFlow creates NUnit tests).

Adding the first feature file

I’ve already got a few implementations of algorithms to calculate Pi. I just want a simple UI that allows you to select an algorithm, type how many decimal places you want Pi calculated to, and then hit “Calculate”. I want the “Calculate” link/button to call back to my application via AJAX (I want to see how the headless browser copes with Javascript).

Step 1: Add a feature file

A feature file is just a text file that describes the functionality you want to implement. It has a simple Given-When-Then structure that should be understandable by non-developers. Hence, they could be created by Business Analysts, etc.

I created a new class library called Spec and added a feature file using the “Add New Item” Dev Studio menu. You’ll see the option “SpecFlow Feature File”.

Feature: Calculate Pi using various algorithms
	I want to be able to view Pi to varying decimal places

Scenario: Display Pi using Taylor series and Bucknall's BigNumber to many decimal places
	Given I visit the Pi.NET website
	And I have selected the 'Bucknall Big Number' algorithm
	And I have entered 500 decimal places
	When I press Go
	Then Pi should be displayed to the correct number of decimal places
	And Calculation statistics should be displayed

You’ll also need to add DLL references to TechTalk.SpecFlow.dll and nunit.framework.dll

Once you’ve done this, compile and run all the unit tests in your Spec class library. You’ll see output like this:


Given I visit the Pi.NET website
-> No matching step definition found for the step. Use the following code to create one:
[Binding]
public class StepDefinitions {
   [Given(@"I visit the Pi\.NET website")]
   public void GivenIVisitThePi_NETWebsite()
   {
       ScenarioContext.Current.Pending();
   }
}
etc, etc.

SpecFlow has told you exactly what you need to do.

So, let’s copy and paste the code into a new C# file called PiNetSteps:

[Binding]
public class PiNetSteps {
    [Given(@"I visit the Pi\.NET website")]
    public void GivenIVisitThePi_NETWebsite() {
        ScenarioContext.Current.Pending();
    }

    [Given(@"I have selected the 'Bucknall Big Number' algorithm")]
    public void GivenIHaveSelectedTheBucknallBigNumberAlgorithm() {
        ScenarioContext.Current.Pending();
    }

    [Given(@"I have entered 500 decimal places")]
    public void GivenIHaveEntered500DecimalPlaces() {
        ScenarioContext.Current.Pending();
    }

    [When(@"I press Go")]
    public void WhenIPressGo() {
        ScenarioContext.Current.Pending();
    }

    [Then(@"Pi should be displayed to the correct number of decimal places")]
    public void ThenPiShouldBeDisplayedToTheCorrectNumberOfDecimalPlaces() {
        ScenarioContext.Current.Pending();
    }

    [Then(@"Calculation statistics should be displayed")]
    public void ThenCalculationStatisticsShouldBeDisplayed() {
        ScenarioContext.Current.Pending();
    }
}

Now when you run the unit tests you get the output:


Ignored: One or more step definitions are not implemented yet.

We can improve the steps code by parameterizing it with Regex. For example, rather than hard coding “500 decimal places”, let’s make this a parameter. And the algorithm name can also be a parameter.

Making these changes gives:

[Given(@"I have selected the '(.+)' algorithm")]
public void GivenIHaveSelectedAnAlgorithm(string algorithm) {
    ScenarioContext.Current.Pending();
}

[Given(@"I have entered '(.+)' decimal places")]
public void GivenIHaveEnteredTheRequiredNumberOfDecimalPlaces(int numberOfDecimalPlaces) {
    this.numberOfDecimalPlaces = numberOfDecimalPlaces; //store for a later assert
    ScenarioContext.Current.Pending();
}

We’re now at a stage that opens up lots of possibilities. You’ve gone from a feature file to a C# class and you now have options on how deep you want your tests to go. If you system under test is not a web application, you are ready to start implementing it straight away. If your system has lots of external dependencies that are not under your control, you could easily stub them out with a container. Or perhaps you want your tests to go through all the application layers and hit a database or webservice. You could easily introduce test database and webservices if you wish.

In the next post I’ll get Coypu up and running and use it to get my SpecFlow steps class to hit the Selenium headless browser.

Next: BDD with SpecFlow and Coypu (Part 2)

Posted in .NET, Behaviour Driven Development, Test Driven Development | Tagged: , , , | Leave a Comment »