Monday, November 29, 2010

Cucumber + Cuke4Duke + Selenium (WebDriver) = Enlightenment

The journey to a better understanding of Acceptance Test Driven Development (ATDD)/Behavior Driven Development (BDD) has been circuitous at best.  It's a journey that began for me over a year and a half ago.  During that time, I've plodded along, attempting to gain insight into what the community is using to solve the problem of how to effectively test web applications and how to produce test results that make sense to technical and non-technical users.  Our team (QA team), had been - and still is - focused on supporting a number of our web-based projects through the use of automated tests.  For the most part, these tests are supported using tools such as FitNesse and Selenium (either version 1.0.x or 2.0).  We also developed a test harness based on Selenium and JUnit, but that has not worked out too well for our non-technical users. In general, the combination of FitNesse and Selenium has proved to be very successful for us.

Several months back, however, I began wondering if there were other open source tools that could sustain our existing requirements, and in addition, provide a simpler solution for our non-technical users (business analysts, product managers, testers, and management).  My research led me to several solutions that, I felt, might work out:
For reasons I won't delve into here, we opted for Cucumber. Specifically, we opted for the Cucumber + Cuke4Duke + Selenium solution.  We went with Cuke4Duke primarily because our development team uses Java, and because we have several members of our team that are quite familiar with Java. Although the toolkit I opted for promised to help solve some of our issues, getting the various tools installed and configured proved to be much more of a challenge than I had expected - particularly, the Cuke4Duke component.

Further research led me to several articles that helped me piece the puzzle together1.  Once I had our project "mavenized" (a term I often use to indicate a project has been built with Maven), per the information contained in reference 1, I went on to install the necessary gems using the mvn -Dcucumber.installGems=true cuke4duke:cucumber command outlined in references 1 and 2.  Then, I set about meeting with our subject matter expert to collaborate on the various features and scenarios that would make up the initial sanity test. After putting together a handful of scenarios together, we implemented the Java code required to test our web application.

Although I have only worked with Cucumber for a short period of time, it's apparent to me that one of Cucumber's greatest strengths is the ease with which one can move from creating the features and scenarios, to building the step definitions, to implementing the underlying code that ties back to the application under test. And, since the features and scenarios are contained in simple text files, collaboration between technical and non-technical users is simplified - no need to learn a new tool, or familiarize yourself with a special markup language - the scenarios are plain text files that can be easily shared between team members using any editor.  Too doggone simple.  That is when the proverbial "light-bulb went off in my head". Suddenly, I felt enlightened and empowered at the same time.

So, with all that being said, here is a brief guide of what you will need to get Cucumber + Cuke4Duke + Selenium up and running.  The recipe goes something like this

Maven 2.2.x - 1 prerequisite

Cucumber - 1 serving
Cuke4Duke - 1 serving
Selenium 2.0 - 1 serving (as of this writing, Selenium 2 is in its alpha phase)

  1. Create a Maven project - we are currently using Eclipse to handle this.  Essentially, create a project that contains the POM file outlined in reference 1.
  2. Add the following to the POM file's dependencies node in order to provide for Selenium support

    and add the following to the POM file's repositories node

    <id>selenium-repository</id> <url></url>

    and add the following to the POM file's cucumberArgs node

      <cucumberArg>--require ${basedir}/target/test-classes</cucumberArg>

  3. Save the POM file.  Saving the POM file should begin the process of downloading a number of Maven dependencies.  Wait until those dependencies are downloaded, before moving on to the next step.
  4. Execute the following from the command line within the project's root directory (i.e., the POM file's location): mvn -Dcucumber.installGems=true cuke4duke:cucumber.  This should install all the remaining dependencies, including the required gems needed to run Cucumber.
  5. Create a features directory under the project's root directory
  6. Execute the following command from the project's directory to test out your installation:  mvn cuke4duke:cucumber -DcukeArgs="--help".  If all went well, you should see Cucumber's help message.
That should be about it.  Your project should be fully baked. Now, to actually begin using Cucumber, you'll need to create a feature file within the features directory created in step 5 above. I'll use the WebDriver example to illustrate this.  Let's create the features file first. Label the file search_for_cheese.feature and save it to the features directory.

Feature: For the love of cheese
In order to find information about cheese
As a cheese lover
I want the ability to search Google for anything related to cheese

Scenario: The search for cheese
Given I have accessed Google's home page
When I enter the keyword of "Cheese"
And click the Submit button
Then the page title returned should be "Cheese - Google Search"

Now, let's create the class file containing the necessary Selenium code - label it and save it to src/test/java.

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import cuke4duke.annotation.I18n.EN.Given;
import cuke4duke.annotation.I18n.EN.Then;
import cuke4duke.annotation.I18n.EN.When;
import static junit.framework.Assert.assertEquals;

public class CheeseSteps  {
private WebDriver driver;
private WebElement element;

public CheeseSteps() {
 // Create a new instance of the html unit driver
 // Notice that the remainder of the code relies on the interface, 
 // not the implementation.
 this.driver = new HtmlUnitDriver();

@Given("^I have accessed Google's home page$")
public void iHaveAccessedGooglesHomePage() {
 // And now use this to visit Google

@When("^I enter the keyword of \"(.*)\"$")
public void iEnterTheKeyword(String keyword) {
 // Find the text input element by its name
 element = driver.findElement("q"));
 // Enter something to search for

@When("^click the Submit button$")
public void clickTheSubmitButton() {
 // Now submit the form. WebDriver will find the form for us from the element

@Then("^the page title returned should be \"(.*)\"$")
public void thePageTitleReturned(String expectedResults) {
 assertEquals(expectedResults, driver.getTitle());

Essentially, the above code wraps the WebDriver example within the noted Given/When/Then annotations supported by Cucumber.  To see the test in action, from within the project's directory execute the following command: mvn clean integration-test

Post a Comment