Skip to content
This repository was archived by the owner on Nov 16, 2018. It is now read-only.

Cucumber Backgrounder

byrnejb edited this page Aug 13, 2010 · 551 revisions

Or: How I Learned to Stop Worrying and Love Testing

Introduction

This discussion deals principally with the initial set up and use of cucumber features in a Ruby on Rails (RoR) Project. Discussion of Behaviour Driven (BDD), Test Driven (TDD), and Panic Driven Development (SNAFU) can be found elsewhere. Details regarding installing the Cucumber gem and its recommended support tools for RoR are found at Ruby on Rails.

Where to Start?

Given that you have installed the gem called "Cucumber"
  And you have generated a RoR project called "MyProject"
  And your session's working directory is called "MyProject"

When you run "script/generate cucumber"

Then you will create a sub-directory called "features"

The foregoing is written in the style of the feature statements that make up the user interface to cucumber testing. We will return to that topic later. For the moment we deal with the logical arrangement of cucumber files within the context of a RoR project.

An archetypal RoR project directory tree looks like this:

MyProject
|-- README
|-- Rakefile
|-- app
|-- config
|-- db
|-- doc
|-- lib
|-- log
|-- public
|-- script
|-- test
|-- tmp
`-- vendor

Running script/generate cucumber adds this layout to the existing structure:

|-- features
|   |-- step_definitions
|   |   `-- webrat_steps.rb
|   `-- support
|       `-- env.rb

We are now ready to begin testing with cucumber.

Where do I put Tests?

Cucumber divides testing into two parts, the outward facing features and the inward facing steps. As discussed elsewhere, features are descriptions of desired behaviour under predefined conditions and following upon specific events. They are typically used in conjunction with end-user input, and in some cases, may be entirely under end-user (in the form of a domain expert) control. Feature files are given the extension .feature.

Steps, on the other hand, are ruby (.rb) files that contain matchers, snippets of text from the feature files, and blocks of ruby and rails code together with assertions from whatever test system you have installed. Given that cucumber evolved out of RSpec stories it is assumed in the cucumber generator that rspec is available, as is evidenced in the default contents of features/support/env.rb:

# Sets up the Rails environment for Cucumber
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
require 'cucumber/rails/world'
Cucumber::Rails.use_transactional_fixtures

# Comment out the next line if you're not using 
# RSpec's matchers (should / should_not) in your steps.
require 'cucumber/rails/rspec'

You are not limited to this however. If you choose to use Rails built-in TestUnit, or any other testing suite, then you may do that by extending the cucumber working environment as explained in Using Test::Unit.

The default layout of the features directory is fairly flat. However, cucumber is programmed with the flexibility to support a much more expressive directory structure. For instance:

|-- features
|   |-- entities
|   |   |-- entity.feature
|   |   `-- step_definitions
|   |       |-- anything.rb
|   |       `-- entity_steps.rb
|   |-- locations
|   |   |-- location.feature
|   |   `-- step_definitions
|   |       `-- location_steps.rb
|   |-- sites
|   |   `-- step_definitions
|   |-- step_definitions
|   |   `-- webrat_steps.rb
|   `-- support
|       `-- env.rb

In this case the bland initial setup has been divided into sub-directories informed by model-centric testing. However this could equally well have been broken up in to model/controller/view hierarchies:

|-- features
|   |-- models
|   |   |-- entities
|   |   |   |-- entity.feature
|   |   |   `-- step_definitions
|   |   |       |-- anything.rb
|   |   |       `-- entity_steps.rb
|   |-- views
|   |   |   |-- entity_new
|   |   |   `-- step_definitions
|   |   |       `-- entity_new_steps.rb
|   |-- step_definitions
|   |   `-- webrat_steps.rb
|   `-- support
|       `-- env.rb

It is well to be aware that, regardless of the directory structure chosen, cucumber effectively flattens the test directory when running tests. By this I mean that anything ending in .rb under the start point for a cucumber feature run is searched for feature matches. So that a step contained in features/models/entities/step_definitions/anything.rb can be used in a feature file contained in features/views/entity_new. It is also worth noting that step files can be called anything so long as they end in .rb. Consider, however, that the cucumber feature generator will place the files it produces in conformance with the default layout.

How do I Write Tests?

Detailed discussion of feature writing and step construction are provided elsewhere (and I will provide links when I get around to it). None-the-less, a brief outline is provided here. A Feature is typically contained in its own file (ending in .feature). Each Feature consists of multiple Scenarios. Each Scenario consists of three sections, Given, When and Then. Each section consists of one or more clauses that are used to match test steps. The conventional arrangement is:

Feature: Some terse yet descriptive text
In order to/that <some business value>
A/an <some actor>
Should <some beneficial outcome>
To Increase Revenue | Reduce Costs | Protect Revenue  

  Scenario:   Some determinable business situation
      Given some condition to meet
         And some other condition to meet
       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

  Scenario:  A different situation
      ...

For cucumber the key words use here are Feature, Scenario, Given, When, Then, and And. Feature is used to provide identification of the test group when results are reported. Scenario is used in the same fashion. Given, When, Then, and And are cucumber methods that take as their argument the string that follows. They are also all equivalent:


      def Given(name)
        create_step('Given', name, *caller[0].split(':')[1].to_i)
      end
def When(name)
create_step(‘When’, name, *caller0.split(‘:’)1.to_i)
end
def Then(name)
create_step(‘Then’, name, *caller0.split(‘:’)1.to_i)
end
def And(name)
create_step(‘And’, name, *caller0.split(‘:’)1.to_i)
end

If ever there was a case for method_alias then this has to be it.

The string from each one of these method is compared against the matchers contained in the step.rb files. A step matcher looks much like this:

Given /there are (\d+) froobles/ do |n|
  Frooble.transaction do
    Frooble.destroy_all
    n.to_i.times do |n|
      Frooble.create! :name => "Frooble #{n}"
    end
  end
end

The significant thing here is that the method (Given) takes as its argument a regexp bounded by /. For example, in the features given above we had the statement: And some other action. This could be matched by any of the following step matchers in any step file under the features directory.

Given /some other action/ do
When /some (.*) action/ do |match|
Then /(*.) other action/ do |match|
And /some other act.*/ do 
Given /(.*) other (.*)/ do |first,second|

Which raise an interesting point point. If you had all of these matchers defined somewhere then cucumber would tell you that it found multiple matches and force you to distinguish them.

Note that you are not required to use a matched subpattern, (.* ), in the following block. It is considered better form by some to surround with double quotation marks, ", elements in the feature step clauses that meant to be taken as values. This is only a convention and one not followed by many. However, if you choose to follow this road then you must adjust your step files accordingly. For example:

Given Some determinable "business" situation

Given /determinable "(.*)" situation/ do |s|

Finally, you can have steps call steps. For example:

And /some "(.*)" action  do |a|
   ...
end

And /in an invoiced non-shipped situation/ do
  And "some \"invoiced\" action" 
  And "some \"non-shipped\" action"
  ...
end

Worthy of note is that, inside the step files, one must enclose the arguments to the Given/When/Then/And methods with a string delimiter. Because of this, if you have adopted the practice of demarcating values in the step matchers with double quote marks then you must escape them in the equivalent step strings. You should also be cautious not to include the quote marks in the value matchers, “(.)” is not the same as (.*). If you use quotes in the feature files and do not account for them in your matchers then you will get variables that contain leading and trailing quotes as part of their values.


And some "required" action

And /some (.*) action/ do |a|
  a => "required"

And /some "(.*)" action/ do |a|
  a => required

Clone this wiki locally