Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Cucumber specs / tests without step-definitions
Tree: 3ef898ab2a

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.

Cuke Salad

Cucumber, washed and ready to eat for Friction-free ATDD/BDD

This is a work in progress - feedback welcome e-mail feedback to

** ToDo: **

  • Support more step structures - such as tables as input
  • Move beyond current examples by documenting CukeSalad with Cucumber
  • Remembering data between steps
  • Multiple role/actor scenarios

This project has step-free access!

CukeSalad allows you to focus on the task at hand - expressing examples, the roles involved in those examples and what those roles can do with the product under development.

With CukeSalad you don't need to write step-definitions.

Of course, you still have to write some code - but only the code that expresses:

  • the roles and the actions they can perform
  • the tasks and the actions involved in completing that task


The terms "actions" and "tasks" above come from Task Analysis, as used in User Centred Design (UCD) of Human Computer Interfaces (HCI) a.k.a. User Experience (UX):

  • Goal: What we’re trying to achieve which has one or more…
  • Tasks: The high-level work-item that we complete to fulfil the goal, each having one or more…
  • Actions: The specific steps or interactions we execute to complete the task.

More information about Goals, Tasks and Actions can be found in this blog post

Let's see how this works with a simple example...


gem install cukesalad

Let's Get started

Create a new project Calculator:

cukesalad new Calculator

Or configure an existing project by running the following command inside the project directory

cukesalad configure

In idiomatic Cucumber style, we use features/support/env.rb to require CukeSalad and define the location of our project's roles and tasks e.g.:

require 'cukesalad'
begin require 'rspec/expectations'; rescue LoadError; require 'spec/expectations'; end

Cucumber will automatically find our project's roles and tasks, as it loads all .rb files beneath the project's features/ directory.

Write Features

In features/, let's create our first feature file - a_place_to_start.feature:

Feature: A Place To Start
As Callie, a calculating individual
I want to know when my calculator is on
So that I know when I can start calculating

Scenario: Let's Begin
  Given I am a Calculating Individual
  When  I attempt to switch on the calculator
  Then  I should see the answer '0'

Let's take a moment to understand this scenario:

Scenario: Let's Begin
  Given I am a <some role>
  When  I attempt to <do some task>
  Then  I should <ask some question> '<expected answer>'

To get this working, we don't need to write any steps. Just explain how to do the task using a class...

Create Tasks

Explaining how to do a task is easy: Create a new file, features/lib/tasks/switch_on_the_calculator.rb

Remember the step When I attempt to switch on the calculator

in_order_to "SwitchOnTheCalculator" do
  @calc = switch_on_the_calculator

Remember the step Then I should see the answer '0' Now we need task/see_the_answer.rb

in_order_to "SeeTheAnswer" do

Now we've explained the tasks, we need to define the role that performs them. In this example, we need to explain how the CalculatingIndividual role works...

Create Roles

Remember the step Given I am a Calculating Individual?

We explain a role by creating a new file called features/lib/roles/calculating_individual.rb

module CalculatingIndividual

  def switch_on_the_calculator
    @calculator =

  def look_at_the_display

You'll need a class called Calculator on the load path of course, but that's it.

From your project folder, run (note: '%' is our command prompt)

% cucumber

We now have our first passing Feature, without creating a single step definition!

Wash, rinse, repeat

Let's try another scenario...

Scenario Outline: Find the sum of two numbers
  Given I am a Calculating Individual
  And I was able to switch on the calculator
  When I attempt to add: the number '10' to the number '10'
  Then I should see the answer '20'

Notice that we've reused 'switch on the calculator'.

The new When step has a slightly different layout. Let's examine that for a second... Notice the ':' (colon) after and the name-value pairs:

When I attempt to <do something>: <name> '<value>' <name> '<value>'

You can have as many name-value pairs as you like.

So, we need a task called tasks/add.rb that explains the individual actions required to complete the task:

in_order_to "Add" do
  enter @value_of(:the_number)
  press :plus
  enter @value_of(:to_the_number)
  press :equals

Notice how the value_of lines use symbols that correspond to the wording 'the number '10' to the number '10' in the "When" step.

There is some 'syntactic sugar' that we can use to dress this up a little and make it read nicer... a simple attribute mapping:

in_order_to "Add", the_number: :first_number, to_the_number: :second_number do
    enter the :first_number
    press :plus
    enter the :second_number
    press :equals

All we've done is mapped "the_number" to "first_number". There is a special method called "the" that allows you to reference the mapped values rather than the symbols derived from the scenario.

Now all we need to do is modify our calculating_individual.rb to receive those calls...

module CalculatingIndividual

  def switch_on_the_calculator
    @calculator =
    @operate_with = {
      plus: :+,
     minus: :-

  def enter value
    @calculator.enter value.to_i

  def press next_operator
    if next_operator == :equals
      @calculator.get_ready_to @operate_with[next_operator]

  def equals

  def look_at_the_display

Now, you can run cucumber again:

% cucumber

There's no need to write step_definitions... Simply express the roles and the tasks in clear, concise, easy to read classes.

After adding some more scenarios and tasks and an alternative "Calculating Individual" (see below for why), our finished Calculator directory structure looks like this...

├── cucumber.yml
├── features
│   ├── A_PlaceToStart.feature
│   ├── Addition.feature
│   ├── Complex_calculations.feature
│   ├── LOOK_MA_NO_STEP_DEFS.txt
│   ├── Subtraction.feature
│   ├── Typical_workflow.feature
│   ├── lib
│   │   ├── alternative
│   │   │   ├── roles
│   │   │   │   └── calculating_web_user.rb
│   │   │   └── tasks
│   │   └── default
│   │       ├── roles
│   │       │   └── calculating_individual.rb
│   │       └── tasks
│   │           ├── add.rb
│   │           ├── perform.rb
│   │           ├── see_the_answer.rb
│   │           ├── subtract.rb
│   │           └── switch_on_the_calculator.rb
│   └── support
│       └── env.rb
├── lib
│   ├── calculator.rb
│   └── web_calculator.rb
└── spec
    ├── calculator_spec.rb
    └── web_calculator_spec.rb

Take a look around the examples and the code to see how it all works. We hope you enjoy reading as much as we enjoyed writing it.

Alternative Roles

As our features describe the value of a calculator application and not its implementation, we have the opportunity to reuse them.

In the Calculator example, we create a new role in ./features/lib/alternative/roles/calculating_web_user.rb, which we can swap into our tests using a Cucumber profile defined in features/cucumber.yml:

default --exclude features/lib/alternative/
alternative -r features/lib/alternative/ -r features/support/env.rb -r features/lib/default/tasks/

We can run our alternative configuration like so:

%cucumber --profile alternative

The Calculating Web User masquerades as the Calculating Individual from our previous example, and provides the same API, allowing us to reuse all of our existing features and tasks.

The alternative, ./lib/web_calculator.rb, implementation is a simple Sinatra application, which we drive with the Capybara web testing framework.

By writing a single new role class we're able to reuse all of our existing features, tasks and even the Calculator itself, which the web calculator delegates to in order to do its calculations.

Something went wrong with that request. Please try again.