<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>The next 10,000 hours &#187; cucumber</title>
	<atom:link href="http://www.trouble.net.au/blog/korny/tag/cucumber/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.trouble.net.au/blog/korny</link>
	<description>Korny&#039;s tech blog</description>
	<lastBuildDate>Sat, 04 Jun 2011 01:07:47 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Behaviour Driven Development with Cucumber and Selenium</title>
		<link>http://www.trouble.net.au/blog/korny/2009/11/03/behaviour-driven-development-with-cucumber-and-selenium/</link>
		<comments>http://www.trouble.net.au/blog/korny/2009/11/03/behaviour-driven-development-with-cucumber-and-selenium/#comments</comments>
		<pubDate>Tue, 03 Nov 2009 10:22:08 +0000</pubDate>
		<dc:creator>korny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[bdd]]></category>
		<category><![CDATA[cucumber]]></category>
		<category><![CDATA[selenium]]></category>
		<category><![CDATA[tdd]]></category>
		<category><![CDATA[testing]]></category>

		<guid isPermaLink="false">http://www.trouble.net.au/blog/korny/?p=102</guid>
		<description><![CDATA[[Please note - if you are familiar with BDD, Cucumber, or Selenium, parts of this may be a tad basic - but I thought it was worth writing a from-scratch guide for those to whom this is unfamiliar territory...] What is Behaviour Driven Development? BDD is a new-ish term used as a contrast to Test [...]]]></description>
			<content:encoded><![CDATA[<p>[Please note - if you are familiar with BDD, Cucumber, or Selenium, parts of this may be a tad basic - but I thought it was worth writing a from-scratch guide for those to whom this is unfamiliar territory...]</p>
<h3>What is Behaviour Driven Development?</h3>
<p>BDD is a new-ish term used as a contrast to Test Driven Development &#8211; it was coined by Dan North in 2006, as described in his article at <a href="http://dannorth.net/introducing-bdd" target="_blank">http://dannorth.net/introducing-bdd</a> &#8211; there&#8217;s also a good introduction in <a href="http://en.wikipedia.org/wiki/Behavior_Driven_Development" target="_blank">wikipedia</a>.</p>
<p>In a nutshell, BDD is all about describing the behaviour of an application, in a plain-text language that can be understood by end users, testers, and developers &#8211; and then hopefully automating acceptance testing, so you can prove that at any time, the application meets the BDD description.<br />
<span id="more-102"></span></p>
<h3>How about Cucumber? and Selenium?</h3>
<p><a href="http://cukes.info/" target="_blank">Cucumber</a>, which grew out of the Ruby <a href="http://rspec.info/">Rspec</a> framework, is a tool to enable the writing of acceptance criteria in a controlled plain-text format, and then running those criteria via some ruby back-end code.</p>
<p><a href="http://seleniumhq.org/" target="_blank">Selenium</a> is a set of tools for automating tests in web browsers.  There are actually two main flavours of Selenium, Selenium IDE that runs in a browser-based gui, and Selenium RC, a client/server version with a java server and clients in a number of languages. We&#8217;ll use Selenium RC, with the Ruby front end (as it makes integrating with Cucumber very easy).  You can also use alternative tools such as <a href="http://watir.com/" target="_blank">Watir</a> or <a href="http://celerity.rubyforge.org/" target="_blank">Celerity</a> to do similar things in different ways; each has it&#8217;s merits and limitations.</p>
<h3>Ok, give me an example</h3>
<p>I&#8217;ve used the Twitter home page as an example, as it has a bunch of asynchronous ajax behaviour, which is very hard to test without automating a web browser.</p>
<p>The scenarios for a related set of Twitter features are defined in a feature file, such as <code><strong>trending_topics.feature</strong></code> :</p>
<pre>Feature: Twitter trending topics
In order to tap into the zeitgeist
as a web surfer
I should be able to see what's being discussed on twitter

Scenario: view popular topics
When I visit the Twitter home page
Then I should be able to see popular topics right now

Scenario: search for most popular topics
Given I am on the Twitter home page
And I see the most popular topic
When I search for the most popular topic
Then I should see results containing the most popular topic
And I should see a message indicating more results exist within 40 seconds</pre>
<p>The first few lines are a preamble &#8211; they define the background of the feature.</p>
<p>The actual scenarios are runnable definitions of how the feature actually behaves.</p>
<h3>Making the features runnable</h3>
<p>There are several layers of code to make these features actually runnable.</p>
<p>First, we have step definitions &#8211; these are code that matches each &#8220;Given&#8221;, &#8220;When&#8221; or &#8220;Then&#8221; step with a regular expression or a plain text string, that identifies the matching ruby logic, and possibly extracts user parameters.  For example, the step &#8220;And I should see a message indicating more results exist within 40 seconds&#8221; is matched by ruby code like:</p>
<pre class="brush: ruby">
Then /^I should see a message indicating more results exist within (\d+) seconds$/ do |timeout|
</pre>
<p>- the regular expression matches the step, and pulls out the timeout parameter into a variable.</p>
<p>The full code of the steps file is as follows:</p>
<p><code><strong>twitter_steps.rb</strong></code></p>
<pre class="brush: ruby">
Given &quot;I am on the Twitter home page&quot; do
  @home_page.visit
end

Given &quot;I see the most popular topic&quot; do
  @most_popular = @home_page.most_popular_topic
  puts &quot;Most popular topic: &#039;#{@most_popular}&#039;&quot;
end

When &quot;I visit the Twitter home page&quot; do
  Given &quot;I am on the Twitter home page&quot;
end

When &quot;I search for the most popular topic&quot; do
  @home_page.search(@most_popular)
end

Then &quot;I should be able to see popular topics right now&quot; do
  @home_page.should have_popular_topics
end

Then &quot;I should see results containing the most popular topic&quot; do
  @results_page.tweets.each do |result|
     result[:tweet].downcase.should include @most_popular.downcase
  end
end

Then /^I should see a message indicating more results exist within (\d+) seconds$/ do |timeout|
  @results_page.wait_for_more_results(timeout.to_i)
end
</pre>
<p>As you can see, the step implementations are very simple &#8211; all the heavy lifting is done by page model objects &#8211; @home_page and @results page wrap all the logic related to two different web pages.  The twitter_steps.rb file just handles matching the steps, and tracking some state, such as the @most_popular variable.</p>
<p><code><strong>twitter_home_page.rb</strong></code> defines the home page model:</p>
<pre class="brush: ruby">
class TwitterHomePage
  PAGE_URL = &quot;http://www.twitter.com&quot;
  def initialize(world)
    @world = world
    @browser = $selenium_helper.browser
  end

  def visit
    @browser.open PAGE_URL
    @browser.wait_for_page_to_load
    @browser.title.should == &quot;Twitter&quot;
  end

  def has_popular_topics?
    @browser.element? POP_TOPICS_LOCATOR
  end

  def most_popular_topic
    @browser.text FIRST_POP_TOPIC_LOCATOR
  end

  def search(topic)
    @browser.type SEARCH_BOX_LOCATOR, topic
    @browser.click SEARCH_BUTTON_LOCATOR
    @browser.wait_for_element RESULTS_HEADING_LOCATOR
  end

  private

  POP_TOPICS_LOCATOR = %Q{//div[@id = &quot;trends&quot;]//div[@class = &quot;current&quot;]}
  FIRST_POP_TOPIC_LOCATOR = &quot;#{POP_TOPICS_LOCATOR}/ul/li[1]/a&quot;
  SEARCH_BOX_LOCATOR = %Q{//input[@id=&quot;home_search_q&quot;]}
  SEARCH_BUTTON_LOCATOR = %Q{//a[@id=&quot;home_search_submit&quot;]}
  RESULTS_HEADING_LOCATOR = %Q{//div[@id=&quot;content&quot;]/h2[@id=&quot;timeline_heading&quot;]}
end
</pre>
<p><code><strong>twitter_results_page.rb</strong></code> is for handling search results:</p>
<pre class="brush: ruby">
class TwitterResultsPage
  def initialize(world)
    @world = world
    @browser = $selenium_helper.browser
  end

  def tweets
    # ajax results, give them a chance to load
    @browser.wait_for_element(TIMELINE_LOCATOR)
    result_count = @browser.get_xpath_count(TIMELINE_LOCATOR).to_i

    (1..result_count).collect do |count|
      {
        :author =&gt; @browser.get_text(tweet_author_locator(count)),
        :tweet =&gt; @browser.get_text(tweet_text_locator(count))
      }
    end
  end

  def wait_for_more_results(timeout_secs)
    @browser.wait_for_element(MORE_RESULTS_LOCATOR,{:timeout_in_secs =&gt; timeout_secs})
  end

  private

  def tweet_author_locator(count)
    %Q{#{tweet_locator(count)}//a[contains(@class, &quot;screen-name&quot;)]}
  end

  def tweet_text_locator(count)
    %Q{#{tweet_locator(count)}//span[contains(@class, &quot;msgtxt&quot;)]}
  end

  def tweet_locator(count)
    %Q{#{TIMELINE_LOCATOR}[#{count}]}
  end

  TIMELINE_LOCATOR = %Q{//ol[@id=&quot;timeline&quot;]/li}
  MORE_RESULTS_LOCATOR = %Q{//div[@id=&quot;new_results_notification&quot;]/a[@id=&quot;results_update&quot; and not(contains(@style, &quot;display: none&quot;))]}
end
</pre>
<p>The page models use a lot of xpath locators to find items in web pages; they work well on most modern browsers but might be inconsistent on IE6 &#8211; if you want to test on IE6 you might have to look into other ways to locate elements, such as finding them by ID.</p>
<p>There are some utility classes that set up the environment, user configuration, and the Selenium interface:</p>
<p><code><strong>env.rb</strong></code> is the main entry point for Cucumber &#8211; it is loaded first, before any other ruby files, and it sets up globals and startup/shutdown code:</p>
<pre class="brush: ruby">
BASEDIR = File.join(File.dirname(__FILE__),&quot;..&quot;) unless defined? BASEDIR
PRJDIR = File.join(File.dirname(__FILE__),&quot;..&quot;,&quot;..&quot;,&quot;..&quot;) unless defined? PRJDIR
require &#039;spec/expectations&#039; # rspec extras
require File.join(BASEDIR,&#039;support/selenium_helper.rb&#039;)
require File.join(BASEDIR,&#039;support/user_config.rb&#039;)

# globals - keep these to a minimum!
$user_config = UserConfig.new
$selenium_helper = SeleniumHelper.new($user_config)  # better a global than a singleton - still need something global as it&#039;s used in monkey-patching bits below

at_exit do
  $stderr.puts &quot;global exit block - closing browser&quot;
  $selenium_helper.shutdown
end

module MyWorld
  # add methods here you want accessible from all cucumber steps
end

World(MyWorld)

Before do
  @home_page = TwitterHomePage.new(self)
  @results_page = TwitterResultsPage.new(self)
end
</pre>
<p>The &#8216;MyWorld&#8217; module is there as a starting point for your own extensions &#8211; generally I have often-needed functions in this module; see <a href="http://wiki.github.com/aslakhellesoy/cucumber/a-whole-new-world">the cucumber documentation</a> for more.</p>
<p>The &#8216;Before&#8217; block is called before every scenario &#8211; here it just sets up the page objects, but other per-scenario stuff can also be added here.</p>
<p>Other than that, the main things included are a user config class, that loads user configuration from a file named for the user&#8217;s host name (so on &#8220;my_pc&#8221; it will load a config file called &#8220;config/my_pc.config&#8221;); see <a href="http://gist.github.com/224106">http://gist.github.com/224106</a> for code and a typical example.</p>
<p>&#8230; and the real work of loading Selenium is in the <strong><code>selenium_helper.rb</code></strong> file:</p>
<pre class="brush: ruby">
require &#039;selenium/client&#039;
require &#039;selenium/rspec/spec_helper&#039;
require File.join(BASEDIR,&#039;support/user_config.rb&#039;)

if defined? JRUBY_VERSION
  # jruby has it&#039;s own process-handling code, as &#039;fork&#039; is unreliable in java
  require &#039;java&#039;
end

class SeleniumHelper
  attr_accessor :max_timeout, :browser
  def initialize(user_config)
    @user_config = user_config
    @max_timeout = 45  # maximum allowed timeout
    @selenium_port = user_config[&#039;selenium.port&#039;]
    @selenium_browser = @user_config[&#039;selenium.browser.name&#039;]
    @selenium_process = nil
    @browser = nil
    start_selenium
    start_browser
  end

  def shutdown
    stop_selenium
  end

  private

  def start_selenium
    selenium_jar_path = File.expand_path(File.join(PRJDIR,&quot;lib&quot;,&quot;selenium&quot;,&quot;selenium-server.jar&quot;))
    raise &quot;Can&#039;t find #{selenium_jar_path}&quot; unless File.exists?(selenium_jar_path)
    cmd = &quot;java -jar #{selenium_jar_path} -timeout #{@max_timeout} -port #{@selenium_port}&quot;

    if defined? JRUBY_VERSION # java magic to run a process
      @selenium_process = java.lang.ProcessBuilder.new(cmd.split(&quot; &quot;)).redirectErrorStream(true).start
      # spawn a thread to redirect background process to log file
      $stderr.puts &quot;selenium process started in background&quot;
      output_stream_to_log(@selenium_process.getInputStream, &quot;selenium.log&quot;)
    else # not java - use fork
      @selenium_process = Process.fork do
          # Note: you need to redirect stdout this way
          # - if you try using &quot;cmd &gt; selenium.log&quot; ruby spawns a subprocess, which you can&#039;t kill
         $stdout.reopen(File.new(&quot;selenium.log&quot;, &quot;w&quot;))
         exec cmd
      end
      $stderr.puts &quot;selenium process started with pid #{@selenium_process}&quot;
    end
    sleep 2  # give it a chance to start
    $stderr.puts &quot;Selenium output sent to selenium.log&quot;
  end

  def start_browser
    begin
      base_url = &quot;http://localhost&quot;
      @browser = Selenium::Client::Driver.new(&quot;localhost&quot;, @selenium_port, @selenium_browser, base_url, @max_timeout)
      @browser.start_new_browser_session
    rescue Exception
      stop_selenium
      raise
    end
  end

  def stop_selenium
    $stderr.puts &quot;killing background selenium task&quot;
    # could open http://localhost:selenium_port/selenium-server/driver/?cmd=shutDown - but this is more reliable!
    if defined? JRUBY_VERSION
      @selenium_process.destroy
      $stderr.puts &quot;and waiting...&quot;
      @selenium_process.waitFor
    else
      Process.kill(&quot;HUP&quot;, @selenium_process)
      $stderr.puts &quot;and waiting...&quot;
      Process.wait
    end
    $stderr.puts &quot;dead.&quot;
  end

  def output_stream_to_log(inputStream, logfilename)
    Thread.new do
      File.open(logfilename,&quot;w&quot;) do |f|
        output = java.io.BufferedReader.new(java.io.InputStreamReader.new(inputStream))
        while (line = output.readLine) != nil
          f.puts line
        end
        output.close
      end
    end

  end
end
</pre>
<p>Note you <b>can</b> just load selenium.jar from a command line, and save yourself much of the effort here &#8211; but this will load and unload it for you, which can keep things simpler.  There&#8217;s some complexity involved in getting it to work both in JRuby (which has to use Java&#8217;s ProcessBuilder class) and in vanilla Ruby.</p>
<h3>Building and running this example</h3>
<p>To get this example up and running, you need:</p>
<ul>
<li><a href="http://www.ruby-lang.org">Ruby</a>, or <a href="http://jruby.org">JRuby</a> &#8211; native Ruby is faster, but if you are in the Java world, JRuby can be handy as it&#8217;s 100% java</li>
<li><a href="http://www.rubygems.org">Ruby-gems</a>, the ruby packaging system (included in JRuby)</li>
<li> The ruby gems:
<ul>
<li>rspec (tested with version 1.2.6) &#8211; not essential, but adds many nice matchers and features to cucumber</li>
<li>cucumber (tested with version 0.3.9)</li>
<li>Selenium (tested with version 1.1.14)</li>
<li>selenium-client (tested with version 1.2.15)</li>
</ul>
</li>
<li>Java 1.5 or later (needed for the selenium server)</li>
<li> The selenium-rc server (version 1.0.1 or later)
<ul>
<li>you really only need the selenium-server jar file, everything else is included in the ruby gems above</li>
<li>download selenium-rc from <a href="http://seleniumhq.org/download/">http://seleniumhq.org/download/</a> , unzip selenium-remote-control-???.zip, and extract the file &#8216;selenium-server.jar&#8217;</li>
</ul>
</li>
</ul>
<p>The file structure is as follows:</p>
<ul>
<li> Root folder &#8211; optionally contains a cucumber.yml file (see cucumber docs for more)
<ul>
<li> test/features &#8211; base directory for all feature files
<ul>
<li>/trending_topics.feature</li>
<li> /step_definitions
<ul>
<li>twitter_steps.rb</li>
</ul>
</li>
<li> /support
<ul>
<li>env.rb</li>
<li>selenium_helper.rb</li>
<li>user_config.rb</li>
<li> /pages
<ul>
<li>twitter_home_page.rb</li>
<li>twitter_results_page.rb</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>lib/selenium/selenium-server.jar &#8211; the selenium server itself</li>
</ul>
</li>
</ul>
<p>The complete set of sample files (except the selenium jar file!) is available at <a href='http://sietsma.com/korny/cuke_sample.zip'>http://sietsma.com/korny/cuke_sample.zip</a>.</p>
<h3>Running it!</h3>
<p>To run all the scenarios in the feature file, assuming all the software is installed and in the path as required, run:</p>
<pre>$ cucumber test/features/trending_topics.feature</pre>
<p>The output should be similar to:</p>
<pre>
selenium process started with pid 25298
Selenium output sent to selenium.log
Feature: Twitter trending topics
  In order to tap into the zeitgeist
  as a web surfer
  I should be able to see what's being discussed on twitter

  Scenario: view popular topics                           # test/features/trending_topics.feature:6
    <i>When I visit the Twitter home page</i>                    # test/features/step_definitions/twitter_steps.rb:10
    <i>Then I should be able to see popular topics right now</i> # test/features/step_definitions/twitter_steps.rb:18

  Scenario: search for most popular topics                                     # test/features/trending_topics.feature:10
    <i>Given I am on the Twitter home page</i>                                        # test/features/step_definitions/twitter_steps.rb:1
Most popular topic: '#unseenprequels'
    <i>And I see the most popular topic</i>                                           # test/features/step_definitions/twitter_steps.rb:5
    <i>When I search for the most popular topic</i>                                   # test/features/step_definitions/twitter_steps.rb:14
    <i>Then I should see results containing the most popular topic</i>                # test/features/step_definitions/twitter_steps.rb:22
    <i>And I should see a message indicating more results exist within 40 seconds</i> # test/features/step_definitions/twitter_steps.rb:26

2 scenarios (2 passed)
7 steps (7 passed)
0m33.328s
global exit block - closing browser
killing background selenium task
and waiting...
dead.
</pre>
<p>Note sections in <i>italics</i> are actually green on a terminal, to indicate success.  You can also format output as html, for prettier display, or for embedding in a Continuous Integration report.<br />
&#8212;</p>
<p>There is far more that could be said on this topic &#8211; this is just the tip of the iceberg.  Hopefully this is a useful starting point however!</p>
<p>As mentioned earlier, you can download all the code in this article from <a href='http://sietsma.com/korny/cuke_sample.zip'>http://sietsma.com/korny/cuke_sample.zip</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.trouble.net.au/blog/korny/2009/11/03/behaviour-driven-development-with-cucumber-and-selenium/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

