15 min read

Behavior-driven Development (BDD) is an agile software development practice that enhances the paradigm of Test Driven Development (TDD) and acceptance tests, and encourages the collaboration between developers, quality assurance, domain experts, and stakeholders. Behavior-driven Development was introduced by Dan North in the year 2003 in his seminal article available at http://dannorth.net/introducing-bdd/.

In this article by Unmesh Gundecha, author of Selenium Testing Tools Cookbook, we will cover:

  • Using Cucumber-JVM and Selenium WebDriver in Java for BDD
  • Using SpecFlow.NET and Selenium WebDriver in .NET for BDD
  • Using JBehave and Selenium WebDriver in Java
  • Using Capybara, Cucumber, and Selenium WebDriver in Ruby

(For more resources related to this topic, see here.)

Using Cucumber-JVM and Selenium WebDriver in Java for BDD

BDD/ATDD is becoming widely accepted practice in agile software development, and Cucumber-JVM is a mainstream tool used to implement this practice in Java. Cucumber-JVM is based on Cucumber framework, widely used in Ruby on Rails world.

Cucumber-JVM allows developers, QA, and non-technical or business participants to write features and scenarios in a plain text file using Gherkin language with minimal restrictions about grammar in a typical Given, When, and Then structure.

This feature file is then supported by a step definition file, which implements automated steps to execute the scenarios written in a feature file. Apart from testing APIs with Cucumber-JVM, we can also test UI level tests by combining Selenium WebDriver.

In this recipe, we will use Cucumber-JVM, Maven, and Selenium WebDriver for implementing tests for the fund transfer feature from an online banking application.

Getting ready

  1. Create a new Maven project named FundTransfer in Eclipse.

  2. Add the following dependencies to POM.XML:

    <project 
    
    xsi_schemaLocation="http://maven.apache.org/POM/4.0.0 http://
    maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>FundTransfer</groupId>
    <artifactId>FundTransfer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
    <dependency>
    <groupId>info.cukes</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>1.0.14</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>info.cukes</groupId>
    <artifactId>cucumber-junit</artifactId>
    <version>1.0.14</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.10</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>2.25.0</version>
    </dependency>
    </dependencies>
    </project>

How to do it…

Perform the following steps for creating BDD/ATDD tests with Cucumber-JVM:

  1. Select the FundTransfer project in Package Explorer in Eclipse. Select and right-click on src/test/resources in Package Explorer. Select New | Package from the menu to add a new package as shown in the following screenshot:

  2. Enter fundtransfer.test in the Name: textbox and click on the Finish button.

  3. Add a new file to this package. Name this file as fundtransfer.feature as shown in the following screenshot:

  4. Add the Fund Transfer feature and scenarios to this file:

    Feature: Customer Transfer's Fund
    As a customer,
    I want to transfer funds
    so that I can send money to my friends and family
    Scenario: Valid Payee
    Given the user is on Fund Transfer Page
    When he enters "Jim" as payee name
    And he enters "100" as amount
    And he Submits request for Fund Transfer
    Then ensure the fund transfer is complete with "$100
    transferred successfully to Jim!!" message
    Scenario: Invalid Payee
    Given the user is on Fund Transfer Page
    When he enters "Jack" as payee name
    And he enters "100" as amount
    And he Submits request for Fund Transfer
    Then ensure a transaction failure message "Transfer
    failed!! 'Jack' is not registered in your List of Payees"
    is displayed
    Scenario: Account is overdrawn past the overdraft limit
    Given the user is on Fund Transfer Page
    When he enters "Tim" as payee name
    And he enters "1000000" as amount
    And he Submits request for Fund Transfer
    Then ensure a transaction failure message "Transfer
    failed!! account cannot be overdrawn" is displayed
  5. Select and right-click on src/test/java in Package Explorer. Select New | Package from menu to add a new Package as shown in the following screenshot:

  6. Create a class named FundTransferStepDefs in the newly-created package. Add the following code to this class:

    package fundtransfer.test;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.By;
    import cucumber.annotation.*;
    import cucumber.annotation.en.*;
    import static org.junit.Assert.assertEquals;
    public class FundTransferStepDefs {
    protected WebDriver driver;
    @Before
    public void setUp() {
    driver = new ChromeDriver();
    }
    @Given("the user is on Fund Transfer Page")
    public void The_user_is_on_fund_transfer_page() {
    driver.get("http://dl.dropbox.com/u/55228056/fundTransfer.
    html");
    }
    @When("he enters "([^"]*)" as payee name")
    public void He_enters_payee_name(String payeeName) {
    driver.findElement(By.id("payee")).sendKeys(payeeName);
    }
    @And("he enters "([^"]*)" as amount")
    public void He_enters_amount(String amount) {
    driver.findElement(By.id("amount")).sendKeys(amount);
    }
    @And("he Submits request for Fund Transfer")
    public void He_submits_request_for_fund_transfer() {
    driver.findElement(By.id("transfer")).click();
    Behavior-driven Development
    276
    }
    @Then("ensure the fund transfer is complete with "([^"]*)"
    message")
    public void Ensure_the_fund_transfer_is_complete(String msg) {
    WebElement message = driver.findElement(By.id("message"));
    assertEquals(message.getText(),msg);
    }
    @Then("ensure a transaction failure message "([^"]*)" is
    displayed")
    public void Ensure_a_transaction_failure_message(String msg) {
    WebElement message = driver.findElement(By.id("message"));
    assertEquals(message.getText(),msg);
    }
    @After
    public void tearDown() {
    driver.close();
    }
    }
  7. Create a support class RunCukesTest which will define the Cucumber-JVM configurations:

    package fundtransfer.test;
    import cucumber.junit.Cucumber;
    import org.junit.runner.RunWith;
    @RunWith(Cucumber.class)
    @Cucumber.Options(format = {"pretty", "html:target/cucumber-htmlreport",
    "json-pretty:target/cucumber-report.json"})
    public class RunCukesTest {
    }
  8. To run the tests in Maven life cycle select the FundTransfer project in Package Explorer. Right-click on the project name and select Run As | Maven test. Maven will execute all the tests from the project.

  9. At the end of the test, an HTML report will be generated as shown in the following screenshot. To view this report open index.html in the targetcucumber-htmlreport folder:

How it works…

Creating tests in Cucumber-JVM involves three major steps: writing a feature file, implementing automated steps using the step definition file, and creating support code as needed.

For writing features, Cucumber-JVM uses 100 percent Gherkin syntax. The feature file describes the feature and then the scenarios to test the feature:

Feature: Customer Transfer's Fund
As a customer,
I want to transfer funds
so that I can send money to my friends and family

You can write as many scenarios as needed to test the feature in the feature file. The scenario section contains the name and steps to execute the defined scenario along with test data required to execute that scenario with the application:

Scenario: Valid Payee
Given the user is on Fund Transfer Page
When he enters "Jim" as payee name
And he enters "100" as amount
And he Submits request for Fund Transfer
Then ensure the fund transfer is complete with "$100
transferred successfully to Jim!!" message

Team members use these feature files and scenarios to build and validate the system. Frameworks like Cucumber or JBehave provide an ability to automatically validate the features by allowing us to implement automated steps. For this we need to create the step definition file that maps the steps from the feature file to automation code. Step definition files implement a method for steps using special annotations. For example, in the following code, the @When annotation is used to map the step “When he enters “Jim” as payee name” from the feature file in the step definition file. When this step is to be executed by the framework, the He_enters_payee_name() method will be called by passing the data extracted using regular expressions from the step:

@When("he enters "([^"]*)" as payee name")
public void He_enters_payee_name(String payeeName) {
driver.findElement(By.id("payee")).sendKeys(payeeName);
}

In this method, the WebDriver code is written to locate the payee name textbox and enter the name value using the sendKeys() method.

The step definition file acts like a template for all the steps from the feature file while scenarios can use a mix and match of the steps based on the test conditions.

A helper class RunCukesTest is defined to provide Cucumber-JVM configurations such as how to run the features and steps with JUnit, report format, and location, shown as follows:

@RunWith(Cucumber.class)
@Cucumber.Options(format = {"pretty", "html:target/cucumber-htmlreport",
"json-pretty:target/cucumber-report.json"})
public class RunCukesTest {
}

There’s more…

In this example, step definition methods are calling Selenium WebDriver methods directly. However, a layer of abstraction can be created using the Page object where a separate class is defined with the definition of all the elements from FundTransferPage:

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.CacheLookup;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class FundTransferPage {
@FindBy(id = "payee")
@CacheLookup
Chapter 11
279
public WebElement payeeField;
@FindBy(id = "amount")
public WebElement amountField;
@FindBy(id = "transfer")
public WebElement transferButton;
@FindBy(id = "message")
public WebElement messageLabel;
public FundTransferPage(WebDriver driver)
{
if(!"Online Fund Transfers".equals(driver.getTitle()))
throw new IllegalStateException("This is not Fund
Transfer Page");
PageFactory.initElements(driver, this);
}
}

Using SpecFlow.NET and Selenium WebDriver in .NET for BDD

We saw how to use Selenium WebDriver with Cucumber-JVM for BDD/ATDD. Now let’s try using a similar combination in .NET using SpecFlow.NET. We can implement BDD in .NET using the SpecFlow.NET and Selenium WebDriver .NET bindings.

SpecFlow.NET is inspired by Cucumber and uses the same Gherkin language for writing specs. In this recipe, we will implement tests for the Fund Transfer feature using SpecFlow.NET. We will also use the Page objects for FundTransferPage in this recipe.

Getting ready

This recipe is created with SpecFlow.NET Version 1.9.0 and Microsoft Visual Studio Professional 2012.

  1. Download and install SpecFlow from Visual Studio Gallery http://visualstudiogallery.msdn.microsoft.com/9915524d-7fb0-43c3-bb3c-a8a14fbd40ee.

  2. Download and install NUnit Test Adapter from http://visualstudiogallery.msdn.microsoft.com/9915524d-7fb0-43c3-bb3c-a8a14fbd40ee.

This will install the project template and other support files for SpecFlow.NET in Visual Studio 2012.

How to do it…

You will find the Fund Transfer feature in any online banking application where users can transfer funds to a registered payee who could be a family member or a friend. Let’s test this feature using SpecFlow.NET by performing the following steps:

  1. Launch Microsoft Visual Studio.

  2. In Visual Studio create a new project by going to File | New | Project. Select Visual C# Class Library Project. Name the project FundTransfer.specs as shown in the following screenshot:

  3. Next, add SpecFlow.NET, WebDriver, and NUnit using NuGet. Right-click on the FundTransfer.specs solution in Solution Explorer and select Manage NuGet Packages… as shown in the following screenshot:

  4. On the FundTransfer.specs – Manage NuGet Packages dialog box, select Online, and search for SpecFlow packages. The search will result with the following suggestions:

  5. Select SpecFlow.NUnit from the list and click on Install button. NuGet will download and install SpecFlow.NUnit and any other package dependencies to the solution. This will take a while.

  6. Next, search for the WebDriver package on the FundTransfer.specs – Manage NuGet Packages dialog box.

  7. Select Selenium WebDriver and Selenium WebDriver Support Classes from the list and click on the Install button.

  8. Close the FundTransfer.specs – Manage NuGet Packages dialog box.

Creating a spec file

The steps for creating a spec file are as follows:

  1. Right-click on the FundTransfer.specs solution in Solution Explorer. Select Add | New Item.

  2. On the Add New Item – FundTransfer.specs dialog box, select SpecFlow Feature File and enter FundTransfer.feature in the Name: textbox. Click Add button as shown in the following screenshot:

  3. In the Editor window, your will see the FundTransfer.feature tab.

  4. By default, SpecFlow will add a dummy feature in the feature file. Replace the content of this file with the following feature and scenarios:

    Feature: Customer Transfer's Fund
    As a customer,
    I want to transfer funds
    so that I can send money to my friends and family
    Scenario: Valid Payee
    Given the user is on Fund Transfer Page
    When he enters "Jim" as payee name
    And he enters "100" as amount
    And he Submits request for Fund Transfer
    Then ensure the fund transfer is complete with "$100
    transferred successfully to Jim!!" message
    Scenario: Invalid Payee
    Given the user is on Fund Transfer Page
    When he enters "Jack" as payee name
    And he enters "100" as amount
    And he Submits request for Fund Transfer
    Then ensure a transaction failure message "Transfer
    failed!! 'Jack' is not registered in your List of Payees"
    is displayed
    Scenario: Account is overdrawn past the overdraft limit
    Given the user is on Fund Transfer Page
    When he enters "Tim" as payee name
    And he enters "1000000" as amount
    And he Submits request for Fund Transfer
    Then ensure a transaction failure message "Transfer
    failed!! account cannot be overdrawn" is displayed

Creating a step definition file

The steps for creating a step definition file are as follows:

  1. To add a step definition file, right-click on the FundTransfer.sepcs solution in Solution Explorer. Select Add | New Item.

  2. On the Add New Item – FundTransfer.specs dialog box, select SpecFlow Step Definition File and enter FundTransferStepDefs.cs in the Name: textbox.

  3. Click on Add button. A new C# class will be added with dummy steps. Replace the content of this file with the following code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using TechTalk.SpecFlow;
    using NUnit.Framework;
    using OpenQA.Selenium;
    namespace FundTransfer.specs
    {
    [Binding]
    public class FundTransferStepDefs
    {
    FundsTransferPage _ftPage = new
    FundsTransferPage(Environment.Driver);
    [Given(@"the user is on Fund Transfer Page")]
    public void GivenUserIsOnFundTransferPage()
    {
    Environment.Driver.Navigate().GoToUrl("http://
    localhost:64895/Default.aspx");
    }
    [When(@"he enters ""(.*)"" as payee name")]
    public void WhenUserEneteredIntoThePayeeNameField(string
    payeeName)
    {
    _ftPage.payeeNameField.SendKeys(payeeName);
    }
    [When(@"he enters ""(.*)"" as amount")]
    public void WhenUserEneteredIntoTheAmountField(string
    amount)
    {
    _ftPage.amountField.SendKeys(amount);
    }
    [When(@"he enters ""(.*)"" as amount above his limit")]
    public void WhenUserEneteredIntoTheAmountFieldAboveLimit
    (string amount)
    {
    _ftPage.amountField.SendKeys(amount);
    }
    [When(@"he Submits request for Fund Transfer")]
    public void WhenUserPressTransferButton()
    {
    _ftPage.transferButton.Click();
    }
    [Then(@"ensure the fund transfer is complete with ""(.*)""
    message")]
    public void ThenFundTransferIsComplete(string message)
    {
    Assert.AreEqual(message, _ftPage.messageLabel.Text);
    }
    [Then(@"ensure a transaction failure message ""(.*)"" is
    displayed")]
    public void ThenFundTransferIsFailed(string message)
    {
    Assert.AreEqual(message, _ftPage.messageLabel.Text);
    }
    }
    }

Defining a Page object and a helper class

The steps for defining a Page object and a helper class are as follows:

  1. Define a Page object for the Fund Transfer Page by adding a new C# class file. Name this class FundTransferPage. Copy the following code to this class:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using OpenQA.Selenium;
    using OpenQA.Selenium.Support.PageObjects;
    namespace FundTransfer.specs
    {
    class FundTransferPage
    {
    public FundTransferPage(IWebDriver driver)
    {
    PageFactory.InitElements(driver, this);
    }
    [FindsBy(How = How.Id, Using = "payee")]
    public IWebElement payeeNameField { get; set; }
    [FindsBy(How = How.Id, Using = "amount")]
    public IWebElement amountField { get; set; }
    [FindsBy(How = How.Id, Using = "transfer")]
    public IWebElement transferButton { get; set; }
    [FindsBy(How = How.Id, Using = "message")]
    public IWebElement messageLabel { get; set; }
    }
    }
  2. We need a helper class that will provide an instance of WebDriver and perform clean up activity at the end. Name this class Environment and copy the following code to this class:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using OpenQA.Selenium;
    using OpenQA.Selenium.Chrome;
    using TechTalk.SpecFlow;
    namespace FundTransfer.specs
    {
    [Binding]
    public class Environment
    {
    private static ChromeDriver driver;
    public static IWebDriver Driver
    {
    get { return driver ?? (driver = new
    ChromeDriver(@"C:ChromeDriver")); }
    }
    [AfterTestRun]
    public static void AfterTestRun()
    {
    Driver.Close();
    Driver.Quit();
    driver = null;
    }
    }
    }
  3. Build the solution.

Running tests

The steps for running tests are as follows:

  1. Open the Test Explorer window by clicking the Test Explorer option on Test | Windows on Main Menu.

  2. It will display the three scenarios listed in the feature file as shown in the following screenshot:

  3. Click on Run All to test the feature as shown in the following screenshot:

How it works…

SpecFlow.NET first needs the feature files for the features we will be testing. SpecFlow.NET supports the Gherkin language for writing features.

In the step definition file, we create a method for each step written in a feature file using the Given, When, and Then attributes. These methods can also take the parameter values specified in the steps using the arguments. Following is an example where we are entering the name of the payee:

[When(@"he enters ""(.*)"" as payee name")]
public void WhenUserEneteredIntoThePayeeNameField(string payeeName)
{
_ftPage.payeeNameField.SendKeys(payeeName);
}

In this example, we are automating the “When he enters “Jim” as payee name” step. We used the When attribute and created a method: WhenUserEneteredIntoThePayeeNameField. This method will need the value of the payee name embedded in the step which is extracted using the regular expression by the SpecFlow.NET. Inside the method, we are using an instance of the FundTransferPage class and calling its payeeNameField member’s SendKeysl() method, passing the name of the payee extracted from the step. Using the Page object helps in abstracting locator and page details from the step definition files, making it more manageable and easy to maintain.

SpecFlow.NET automatically generates the NUnit test code when the project is built. Using the Visual Studio Test Explorer and NUnit Test Adaptor for Visual Studio, these tests are executed and the features are validated.

LEAVE A REPLY

Please enter your comment!
Please enter your name here