Cucumber
http://cukes.info

Alkesh Vaghmaria
IPRUG, 1 February 2011

Creative Commons License

Why Cucumber?

Behaviour-Driven Development

Behaviour-Driven Development is about implementing an application by describing its behavior from the perspective of its stakeholders.
http://en.wikipedia.org/wiki/Behavior_Driven_Development
http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/

Outside In

outside in diagram

Example

        # A comment here

        Feature: Some terse yet descriptive text of what is desired
          In order to realize a named business value
          As a explicit system actor
          I want to gain some beneficial outcome which furthers the goal

        @secretlabel
        Scenario: Some determinable business situation
          Given some precondition
          And some other precondition
          When some action by the actor
          And some other action
          And yet another action
          Then some testable outcome is achieved
          And something else we can check happens too
      

Gherkin

Keywords: i18n.yml

        "en":
          name: English
          native: English
          feature: Feature
          background: Background
          scenario: Scenario
          scenario_outline: Scenario Outline|Scenario Template
          examples: Examples|Scenarios
          given: "*|Given"
          when: "*|When"
          then: "*|Then"
          and: "*|And"
          but: "*|But"
      
https://github.com/aslakhellesoy/gherkin/blob/master/lib/gherkin/i18n.yml

Internationalisation

        "en-lol":
          name: LOLCAT
          native: LOLCAT
          feature: OH HAI
          background: B4
          scenario: MISHUN
          scenario_outline: MISHUN SRSLY
          examples: EXAMPLZ
          given: "*|I CAN HAZ"
          when: "*|WEN"
          then: "*|DEN"
          and: "*|AN"
          but: "*|BUT"
      
https://github.com/aslakhellesoy/gherkin/blob/master/lib/gherkin/i18n.yml

Internationalisation

        OH HAI: STUFFING

          MISHUN: CUCUMBR
            I CAN HAZ IN TEH BEGINNIN 3 CUCUMBRZ
            WEN I EAT 2 CUCUMBRZ
            DEN I HAS 2 CUCUMBERZ IN MAH BELLY
            AN IN TEH END 1 CUCUMBRZ KTHXBAI
      

Run Feature

        # Example feature
        Feature: To show how to run cucumber features
          Scenario: Simplest scenario
            When I go to the homepage
            Then I should see "Welcome to Rails"
      
        cucumber features/myfeature.feature
        cucumber features
        cucumber features/myfeature.feature:3
      
        # Example feature
        Feature: To show how to run cucumber features

          Scenario: Simplest scenario  # features/example.feature:3
            Given I am on the homepage # features/step_definitions/steps.rb:1
              TODO (Cucumber::Pending)
              ./features/step_definitions/steps.rb:2:in `/^I am on the homepage$/'
              features/example.feature:4:in `Given I am on the homepage'

        1 scenario (1 pending)
        1 step (1 pending)
        0m0.002s
      

Cucumber-Rails

Rails 2

        ruby script/generate cucumber --rspec --capybara
      

Rails 3

        # Gemfile
        group :test do
          gem 'cucumber-rails'
          gem 'capybara'
          gem 'database_cleaner'
        end
      
        rails generate cucumber:install
      
        create  config/cucumber.yml
        create  script/cucumber
        create  features/step_definitions/web_steps.rb
        create  features/support
        create  features/support/paths.rb
        create  features/support/env.rb
        create  lib/tasks/cucumber.rake
          gsub  config/database.yml
      
https://github.com/aslakhellesoy/cucumber-rails

Profiles

cucumber.yml

        <%
        rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
        rerun_opts = rerun.to_s.strip.empty? ?
          "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" :
          "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
        std_opts = "--format #{ENV['CUCUMBER_FORMAT'] ||
          'progress'} --strict --tags ~@wip"
        %>
        default: <%= std_opts %> features
        wip: --tags @wip:3 --wip features
        rerun: <%= rerun_opts %> --format rerun --out rerun.txt
          --strict --tags ~@wip
      
https://github.com/aslakhellesoy/cucumber/wiki/cucumber.yml

Paths

features/support/paths.rb

        module NavigationHelpers
          def path_to(page_name)
            case page_name

            when /the home\s?page/
              '/'
            else
              begin
                page_name =~ /the (.*) page/
                path_components = $1.split(/\s+/)
                self.send(path_components.push('path').join('_').to_sym)
              rescue Object => e
                raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
                  "Now, go and add a mapping in #{__FILE__}"
              end
            end
          end
        end

        World(NavigationHelpers)
      

database.yml

        development:
          adapter: sqlite3
          database: db/development.sqlite3
          pool: 5
          timeout: 5000

        test: &test
          adapter: sqlite3
          database: db/test.sqlite3
          pool: 5
          timeout: 5000

        production:
          adapter: sqlite3
          database: db/production.sqlite3
          pool: 5
          timeout: 5000

        cucumber:
          <<: *test
      

Output Formats

HTML Output

HTML Output

Capybara

https://github.com/jnicklas/capybara

selection of steps in: features/step_definitions/web_steps.rb

        Given I am on <page_name>
        when I go to <page_name>
        When I press <button>
        When I follow <link>
        When I fill in "<field>" with "<value>"
        When I fill in "<value>" for "<field>"
        When I select "<value>" from "<field>"
        When I check "<field>"
        When I choose "<field>"
        Then I should see "<text>"
        Then I should see /<regex>/
        Then I should not see "<text>"
        Then I should not see /<regex>/
        Then the "<field>" field should contain "<value>"
        Then the "<label>" checkbox should be checked
        Then I should be on <page_name>
        Then I should have the following query string <expected_pairs>
        Then show me the page
      
https://github.com/jnicklas/capybara

Capybara drivers

Run Feature

        cucumber features/myfeature.feature
      
        Using the default profile...
        ..

        1 scenario (1 passed)
        2 steps (2 passed)
        0m0.192s
      

Factory Girl

Factories

        Factory.define :user do |user|
          user.email                 { Factory.next(:email) }
          user.password              { "password"   }
          user.password_confirmation { "password"   }
        end

        Factory.define :author, :parent => :user do |author|
          author.after_create { |a| Factory(:article, :author => a) }
        end

        Factory.define :recruiter, :parent => :user do |recruiter|
          recruiter.is_recruiter { true }
        end
      

Cucumber

        Given a user exists
        Given an author exists with an email of "author@example.com"
        Given the following recruiter exists:
          | email            | phone number | employer name |
          | bill@example.com | 1234567890   | thoughtbot    |
      
http://robots.thoughtbot.com/post/284805810/gimme-three-steps

Scenario Outlines

        Scenario Outline: eating
          Given there are <start> cucumbers
          When I eat <eat> cucumbers
          Then I should have <left> cucumbers

          Examples:
            | start | eat | left |
            |  12   |  5  |  7   |
            |  20   |  5  |  15  |
      
https://github.com/aslakhellesoy/cucumber/wiki/Scenario-outlines

Background

        Feature: Multiple site support
          As a Mephisto site owner
          I want to host blogs for different people
          In order to make gigantic piles of money

          Background:
            Given a global administrator named "Greg"
            And a blog named "Greg's anti-tax rants"
            And a customer named "Dr. Bill"
            And a blog named "Expensive Therapy" owned by "Dr. Bill"

          Scenario: Dr. Bill posts to his own blog
            Given I am logged in as Dr. Bill
            When I try to post to "Expensive Therapy"
            Then I should see "Your article was published."

          Scenario: Dr. Bill tries to post to somebody else's blog, and fails
            Given I am logged in as Dr. Bill
            When I try to post to "Greg's anti-tax rants"
            Then I should see "Hey! That's not your blog!"

          Scenario: Greg posts to a client's blog
            Given I am logged in as Greg
            When I try to post to "Expensive Therapy"
            Then I should see "Your article was published."
      
https://github.com/aslakhellesoy/cucumber/wiki/Background

Hooks

support/hooks.rb

        Before do
          # Do something before each scenario.
        end
      
        After do |scenario|
          if(scenario.failed?)
            subject = "[Project X] #{scenario.exception.message}"
            send_failure_email(subject)
          end
        end
      
        Around('@fast') do |scenario, block|
          Timeout.timeout(0.5) do
            block.call
          end
        end
      
https://github.com/aslakhellesoy/cucumber/wiki/Hooks

Tagged Hooks

OR tags

        Before('@cucumis,@sativus') do
          # This will only run before scenarios tagged
          # with @cucumis OR @sativus.
        end
      

AND tags

        Before('@cucumis', '@sativus') do
          # This will only run before scenarios tagged
          # with @cucumis AND @sativus.
        end
      

Combination tags

        Before('@cucumis,@sativus', '@aqua') do
          # This will only run before scenarios tagged
          # with (@cucumis OR @sativus) AND @aqua
        end
      

Global Hooks

features/support/env.rb or any other file in this directory

        my_heavy_object = HeavyObject.new
        my_heavy_object.do_it

        at_exit do
          my_heavy_object.undo_it
        end
      
        AfterConfiguration do |config|
          puts "Features dwell in #{config.feature_dirs}"
        end
      

Transforms

support file

        Transform /^(-?\d+)$/ do |number|
          number.to_i
        end
      

step definition file

        Then /^a user, named "([^']+)", should have (\d+) followers$/ do |name,count|
          # Without the transform your count object would be a string
          # and not a number
          count.should be_kind_of(Numeric)
          #...
        end
      
https://github.com/aslakhellesoy/cucumber/wiki/Step-Argument-Transforms http://drnicwilliams.com/2009/04/15/cucumber-building-a-better-world-object/

World

features/support/env.rb

        class CustomWorld
          def a_helper
            ...
          end
        end

        World do
          CustomWorld.new
        end
      

step definition file

        module MyHelper
          def some_helper
            ...
          end
        end

        World MyHelper
      
https://github.com/aslakhellesoy/cucumber/wiki/A-Whole-New-World

RCov

lib/tasks/cucumber.rake

        require 'cucumber/rake/task'
        Cucumber::Rake::Task.new(:features) do |t|
          t.rcov = true
          t.rcov_opts = %w{--rails --exclude features,spec,factories,/gems/}
        end
      
https://github.com/aslakhellesoy/cucumber/wiki/Using-RCov-with-Cucumber-and-Rails

Holy Grail

Full stack testing with headless brower

http://oinopa.com/akephalos/
http://robots.thoughtbot.com/post/1658763359/thoughtbot-and-the-holy-grail https://github.com/bernerdschaefer/akephalos/issues#issue/47
https://github.com/smparkes/capybara-envjs
http://labnotes.org/2010/12/30/zombie-js-insanely-fast-full-stack-headless-testing/

Non Ruby/Rails testing

Features as Documentation

Writing better features

Alternatives

http://mrjaba.posterous.com/acceptance-testing-and-cucumber-alternatives

Unencumbered

spec/integration/user_creates_vurl_spec.rb

        Feature "User creates a vurl" do
          Scenario "creating" do
            Given "I am on the home page" do
              executes { visit root_path }

              When "I submit a valid vurl" do
                executes do
                  fill_in "vurl_url", :with => 'http://example.com'
                  click_button 'Vurlify!'
                end

                Then "I should be on the vurl stats page" do
                  current_url.should == stats_url(Vurl.last.slug)
                end

                And "I should see a success message" do
                  response.body.should include('Vurl was successfully created')
                end

                And "my vurl was created" do
                  Vurl.last.url.should == 'http://example.com'
                end
              end
            end
          end
        end
      

Steak

spec/acceptance/main_page_feature.rb

        feature "Main page" do

          background do
            create_user :login => "jdoe"
            login_as "jdoe"
          end

          scenario "should show existing quotes" do
            create_quote :text => "A joke is a very serious thing",
                         :author => "Winston Churchill"

            visit "/"

            page.should have_css(".quote", :count => 1)
            within(:css, ".quote") do
              page.should have_css(".text", :text => "A joke is a very serious thing")
              page.should have_css(".author", :text => "Winston Churchill")
            end
          end
        end
      

RSpec Book

RSpec Book

Cukeup!

London March 24th, 2011 - Cukeup!
http://skillsmatter.com/event/home/cukeup

Thank you!

Any questions?