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

Ingredients
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)

Instructions
  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

    <dependencies>
    <dependency>
    
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium</artifactId>
    <version>2.0a6</version>
    </dependency>
    </dependencies>
    and add the following to the POM file's repositories node

    <repositories>
    <repository>
    
    <id>selenium-repository</id> <url>http://selenium.googlecode.com/svn/repository/</url>
    </repository>
    </repositories>

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


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


  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 CheeseSteps.java 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
 driver.get("http://www.google.com");
}


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


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


@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

Sunday, November 28, 2010

Xargs

Xargs -- a versatile UNIX command that allows you to build and execute command lines from standard input.  Think of it as a slightly more efficient way of processing lists of standard input that certain commands like find have trouble dealing with.  As noted in Wikipedia,


rm /path/*
or
rm `find /path -type f`
will fail with an error message of "Argument list too long" if there are too many files in /path. However this version (functionally equivalent to rm `find /path -type f`) will not fail:
find /path -type f -print0 | xargs -0 rm

I have recently used it to process a list of files, changing the permission of each file to be read-write for the user.

ls /home/user/*.txt | xargs chmod 600

I have also used it in conjunction with Cucumber.  In this particular scenario, I needed some way of producing a single text file that contained all of the features and scenarios, so that I could go over them with a team of developers, testers and business analysts.  At the moment, Cucumber does not support a way (or at least I have not found a method yet) of extracting all of the scenarios into one or more files.  There are some tools that are actively being developed that should alleviate this issue in the future (see Relish and Cucumber-In-The-Yard), but for now, I leveraged xargs.

find features/ -name *.feature -type f -print0 | xargs -0 cat > features_and_scenarios.txt

This finds all files that end with .feature and prints the results to standard output.  It then takes the output and pipes it into the xargs command, which runs it through the cat command and finally writes the output of the cat command to the features_and_scenarios.txt file.

For more information on the xargs command, have a look at the man page.