Beliebte Suchanfragen
|
//

Cucumber: An Introduction for Non-Rubyists – Setup and the Basics

19.8.2013 | 16 minutes of reading time

Out there in the wild, there are a lot of good acceptance test tools and BDD frameworks – and we already had numerous blog posts here at the codecentric blog about the Robot Framework, Fitnesse, JBehave, Geb and others. One big player in this area is still missing: Cucumber. So, it’s time to fill the gap. This post is an introduction to Cucumber, a BDD framework that can be used to write acceptance tests for web applications which are very readable, maintainable and elegant.

This is part one of a two part tutorial on Cucumber. It goes through the setup of Cucumber and introduces the basics of Cucumber and Gherkin. If you are already familiar with Cucumber you can directly skip ahead to part two , which introduces Capybara, Poltergeist and PhantomJS and shows how to use this stack to write acceptance tests for web applications.

Cucumber has an interesting family tree, which goes a little like this: JBehave was ported to Ruby under the name RBehave, which was rewritten from scratch and renamed to RSpec, to which an RSpec Story runner was added, which evolved into Cucumber. So, historically Cucumber is a remote descendant of JBehave. To complete the circle, Cucumber (originally implemented in Ruby) has now been ported back to the JVM with Cucumber JVM . If you are looking for a JVM based BDD framework, both JBehave and Cucumber JVM should be on your list for evaluation. There is a quick and somewhat superficial comparison on Stack Overflow, in case you’re interested.

Cucumber has been ported to a large number of other platforms, too, so nowadays you can use Cucumber with Ruby (the original), Java, .NET, Adobe Flex, Python, Perl, Erlang, PHP, you name it. You could even use the same Gherkin features (see below) on different platforms, although that is probably not a realistic option because the step implementations are platform specific.

Cucumber, as many other BDD frameworks, is rarely used in isolation. In particular, if you want to do any serious web application testing, you’ll need a full stack consisting (at least) of a browser driver and a browser to execute the tests. In most cases you will also not want to use the browser driver directly, but instead use an abstraction layer on top of that, which makes the test code much more readable and maintainable.

Even if you are experienced in writing automated acceptance tests, the options and possible combinations are intimidatingly numerous. On the Ruby platform alone you could combine Cucumber with

  • Selenium WebDriver (via the selenium-webdriver gem),
  • the Watir WebDriver (a layer on top of Selenium WebDriver),
  • Capybara and Webrat,
  • Capybara and Selenium WebDriver or
  • Capybara and Poltergeist (a driver for PhantomJS)

…to just name a few. Additionally, most of these drivers can drive multiple browser, so you need to decide if you want to do headless testing with PhantomJS or Steam or use a “real” browser like Firefox, Chrome or IE for your testing. The bottom line is, if you are evaluating Cucumber for use in your project, you will also need to evaluate the complete stack (or even multiple stacks) which doesn’t make things easier.

This tutorial uses Cucumber on Ruby. In the second part we add Capybara, Poltergeist and PhantomJS to the mix. In my experience, this stack works very well, especially for apps that make heavy use of AJAX.

Never mind if you’re not a rubyist (I don’t consider myself one, either). The target audience for this tutorial are people with no or only little ruby experience. It takes you through the installation and configuration of Cucumber, shows you how to write your first Cucumber feature and how to use Cucumber and Capybara to write beautiful acceptance tests for a web application. The examples should be quite readable without prior ruby knowlegde and it should be possible to translate the code to a different stack.

Setting up Ruby, Cucumber and PhantomJS

Clone the example project (which contains the Gemfile you will need during the setup) by doing

1git clone -b 00_setup https://github.com/basti1302/audiobook-collection-manager-acceptance.git

Then, to set up Cucumber and PhantomJS on your system, follow one of the following Gists. We do not need PhantomJS for this part of the tutorial, so you can leave that for later when you start with part two.

When you have completed the setup, typing cucumber in the top level directory in the example project should give you the following output:

10 scenarios
20 steps
30m0.000s

Note: Since we are using bundler, we should actually type bundle exec cucumber instead of simply typing cucumber. This ensures that the cucumber command is executed in the context of the bundle defined in the Gemfile. As long as you do not have multiple versions of ruby gems installed on your system, there should be no difference. But if Cucumber behaves unexpectedly, try your luck with bundle exec cucumber – or train yourself to always use bundle exec cucumber right from the start, because it is a good habit anyway.

Cucumber Basics

Features and Scenarios

The top most artifacts of Cucumber are called features. They are defined in a DSL called Gherkin that more or less resembles natural language. (How well the features resemble natural language of course depends on the author of the feature.) Feature files have the suffix .feature and live in a directory named features. When the Cucumber executable is started without arguments, it always looks for a subdirectory of the current working directory that has this name – so it’s a good idea to start Cucumber in the parent directory of features, otherwise it will complain.

A feature can contain a description (which is unparsed text) and one or more scenarios (think scenario ~ test). A scenario describes one behavioral aspect of the system under test. It makes assertions that – given that certain preconditions hold – the system behaves or responds in an expected way when a specified action is executed.

A scenario is comprised of steps. Each step is a short sentence that falls into one of the three familiar BDD categories:

  • Given (setting up a precondition for the scenario),
  • When (execute an action in the application under test) and
  • Then (assert the desired outcome)

On a technical level, it does not matter if you prefix a step with Given, When, Then or And (which can also be used for all three categories). You could set up preconditions in a Then step and make assertions in a Given step – but using the conventions correctly makes scenarios much more readable.

Further down the chain, there needs to be a step definition for each step. These live in the directory features/step_definitions. We’ll take a closer look at them later.

Your First Feature

If you want to follow along, put the following code into a file named features/first.feature. Instead of copy/pasting it yourself, you can also use the prepared branch: git checkout 01_first_feature.

Note: All code shown here is contained in the example repository. The repository has several branches that loosely represent the progress of the tutorial. You can get a list of available branches with git branch -r. The branch names are mentioned throughout the text of this tutorial, whenever they are relevant. If you have modified the code in a branch and git complains about your uncommitted changes when you try to do git checkout branchname, you can either throw away your edits with git reset --hard or commit them by doing git add -A, followed by git commit -am"my modifications".

1#encoding: utf-8
2Feature: Showcase the simplest possible Cucumber scenario
3  In order to verify that cucumber is installed and configured correctly
4  As an aspiring BDD fanatic 
5  I should be able to run this scenario and see that the steps pass (green like a cuke)
6 
7  Scenario: Cutting vegetables
8    Given a cucumber that is 30 cm long
9    When I cut it in halves
10    Then I have two cucumbers
11    And both are 15 cm long

Now run the feature. Go to the root directory of the project (the directory directly above features) and type cucumber (or better: bundle exec cucumber). The output should be something like this:

#encoding: utf-8
Feature: Showcase the simplest possible cucumber scenario
  In order to verify that cucumber is installed and configured correctly
  As an aspiring BDD fanatic 
  I should be able to run this scenario and see that the steps pass (green like a cuke)

  Scenario: Cutting vegetables          # features/first.feature:8
    Given a cucumber that is 30 cm long # features/first.feature:9
    When I cut it in halves             # features/first.feature:10
    Then I have two cucumbers           # features/first.feature:11
    And both are 15 cm long             # features/first.feature:12

1 scenario (1 undefined)
4 steps (4 undefined)
0m0.003s

You can implement step definitions for undefined steps with these snippets:

Given(/^a cucumber that is (\d+) cm long$/) do |arg1|
  pending # express the regexp above with the code you wish you had
end

When(/^I cut it in halves$/) do
  pending # express the regexp above with the code you wish you had
end

Then(/^I have two cucumbers$/) do
  pending # express the regexp above with the code you wish you had
end

Then(/^both are (\d+) cm long$/) do |arg1|
  pending # express the regexp above with the code you wish you had
end

If you want snippets in a different programming language,
just make sure a file with the appropriate file extension
exists where cucumber looks for step definitions.

After echoing the feature the result is listed. It complains that we did not yet define the steps that we have used in this feature. Quite true. But Cucumber goes a step further – it even says how we can solve this and implement the steps (as stubs). How exceptionally nice and polite. It also guesses (correctly) that the numbers used in the steps might vary and replaces the literal numbers with capturing groups. So lets just paste Cucumber’s suggestions into a step definition file. (In fact I often run Cucumber intentionally with undefined steps and use the suggestions as a template for the step implementations.)

Matching Steps With Step Definitions

Create the directory features/step_definitions and in this directory the file first_steps.rb. Yes, step definitions are written in ruby – at least when we are using plain vanilla Cucumber. As mentioned before, there are Cucumber ports for a multitude of languages, so chances are that you can write the step definitions in the language you are most comfortable with. However, this blog post uses the original and so we are going to do some ruby now. Don’t be afraid, step definitions don’t need to be overly complicated and even if you are completely new to ruby you’ll be able to learn enough of it to write step definitions as we go along.

The following code will go into features/step_definitions/first_steps.rb (the name does not matter, all .rb files in features/step_definitions will be loaded when running the features).

1#encoding: utf-8
2Given /^a cucumber that is (\d+) cm long$/ do |arg1|
3  pending # express the regexp above with the code you wish you had
4end
5 
6When /^I cut it in havles$/ do
7  pending # express the regexp above with the code you wish you had
8end
9 
10Then /^I have two Cucumbers$/ do
11  pending # express the regexp above with the code you wish you had
12end
13 
14Then /^both are (\d+) cm long$/ do |arg1|
15  pending # express the regexp above with the code you wish you had
16end

Now just run Cucumber again (remember, from the parent directory of features). The output only looks a little bit better:

#encoding: utf-8
Feature: Showcase the simplest possible cucumber scenario
  In order to verify that cucumber is installed and configured correctly
  As an aspiring BDD fanatic 
  I should be able to run this scenario and see that the steps pass (green like a cuke)

  Scenario: Cutting vegetables          # features/first.feature:8
    Given a cucumber that is 30 cm long # features/step_definitions/first_steps.rb:1
      TODO (Cucumber::Pending)
      ./features/step_definitions/first_steps.rb:2:in `/^a cucumber that is (\d+) cm long$/'
      features/first.feature:9:in `Given a cucumber that is 30 cm long'
    When I cut it in halves             # features/step_definitions/first_steps.rb:5
    Then I have two cucumbers           # features/step_definitions/first_steps.rb:9
    And both are 15 cm long             # features/step_definitions/first_steps.rb:13

1 scenario (1 pending)
4 steps (3 skipped, 1 pending)
0m0.005s

The summary tells us that at least one step is pending and so the whole scenario is marked as pending. A scenario is skipped entirely when the first step fails (or is pending), so Cucumber does not even try to execute the other pending steps – that’s why it reports 3 steps as skipped.

Before we implement the steps, let’s take a close look on first_steps.rb. If you have never seen a Cucumber step file before, it might look a little weird. The individual step implementations look a little bit like methods – in fact, they are ruby methods and the code inside is just plain ruby code. But the method header looks a bit queer – it contains a regular expression. That is a very neat feature: When Cucumber needs to find the step definition for a step in a scenario, it checks all step definition files (all .rb files in features/step_definitions, including subdirectories). If it finds one with a matching regular expression, this implementation is called. (By the way, if it finds more than one match, it complains about an ambiguous match, instead of executing the first or an arbitrary step.)

This mechanism has several advantages. The first is flexible and well-readable parameterization of step definitions. We have already seen that in

Given /^a cucumber that is (\d+) cm long$/ do |arg1|

Here the match of the first capturing group (enclosed in parentheses) is passed into the step implementation as the first argument (called arg1 – we should probably rename it to length). The \d+ matches one or more digits, so it is clear that we need to pass a number here.

The regular expression matching also gives you the ability to write scenarios that are nice to read, without duplication in the step file. Here’s a simple example on how to exploit this: We could rewrite the second step as

1When /^I (?:cut|chop) (?:it|the cucumber) in (?:halves|half|two)$/ do
2  pending # express the regexp above with the code you wish you had
3end

With this change all of the following steps would match:

1When I cut the cucumber in halves
2When I chop the cucumber in half
3When I cut it in two

(With regard to vegetable cutting, “cutting in halves” is better english than “cutting in half”, but that’s not the point here.)

We have used non-capturing groups (starting with "(?:") to cater for expressive variety. This is quite common in Cucumber steps.

Once you start using this pattern, you should be careful to not take it too far. Only provide for the step variations that you really need right now. In the end you do not want to spend more time fighting with regular expressions than testing your system. It might even make sense to write two step definitions if it is too difficult to put all variations into one regex. You also have two other options at your disposal to reduce duplication: First, you can put plain ruby methods into the step file and call them from your steps or you can require other ruby files and reuse their code. Finally, you can even call other steps from within a step.

Implementing Steps

Now for what’s inside a step implementation: Currently, all our steps only contain a call to pending. This is like a todo marker. With pending, you can first write you scenario, stub all steps with pending and then implement the steps one after another until the scenario passes. This fits nicely into an outside-in ATDD approach where you drive the implementation of the system under test with acceptance tests:

  1. Write a Cucumber feature first (with pending steps),
  2. implement the first step in Cucumber,
  3. implement the required funcionality in the system under test to make just this step pass,
  4. repeat.

All this said, let’s put some real implementation into the step methods. The code is also available per git checkout 02_step_definitions.

1Given /^a cucumber that is (\d+) cm long$/ do |length|
2  @cucumber = {:color => 'green', :length => length.to_i}
3end
4 
5When /^I (?:cut|chop) (?:it|the cucumber) in (?:halves|half|two)$/ do
6  @choppedCucumbers = [
7    {:color => @cucumber[:color], :length => @cucumber[:length] / 2},
8    {:color => @cucumber[:color], :length => @cucumber[:length] / 2}
9  ]
10end
11 
12Then /^I have two cucumbers$/ do
13  @choppedCucumbers.length.should == 2
14end
15 
16Then /^both are (\d+) cm long$/ do |length|
17  @choppedCucumbers.each do |cuke|
18    cuke[:length].should == length.to_i
19  end
20end

If you run Cucumber again, you should have the following output:

#encoding: utf-8
Feature: Showcase the simplest possible cucumber scenario
  In order to verify that cucumber is installed and configured correctly
  As an aspiring BDD fanatic 
  I should be able to run this scenario and see that the steps pass (green like a cuke)

  Scenario: Cutting vegetables          # features/first.feature:8
    Given a cucumber that is 30 cm long # features/step_definitions/first_steps.rb:3
    When I cut it in halves             # features/step_definitions/first_steps.rb:7
    Then I have two cucumbers           # features/step_definitions/first_steps.rb:14
    And both are 15 cm long             # features/step_definitions/first_steps.rb:18

1 scenario (1 passed)
4 steps (4 passed)
0m0.005s

Yay! Now all steps are printed in green – like a cucumber (that’s why the tool has been named after the fruit).

Let’s review the implementation: In ruby, an identifier that begins with @ is an instance variable. You do not need to declare them up front, if you assign a value to it at runtime, the instance variable is defined on the fly. That’s why @cucumber is visible in all steps and can be used to keep some state across step boundaries. The expressions enclosed in curly braces are hashes (aka dictionaries or associative arrays, think java.util.Map if you’re coming from the JVM) and the identifiers beginning with a colon are the keys (the colon makes them a symbol , but you don’t need to worry about that for now).

The expressions @choppedCucumbers.length.should == 2 and cuke[:length].should == length.to_i might require a bit of explanation. What we are using here is the RSpec module Spec::Expectations. In features/support/env.rb we have the line require 'rspec/expectations', thus we have the module at our disposal in all our step files. This module gives us the ability to express expectations on any object by adding methods to all objects (by monkey patching). We are using object expectations here, which provides methods like should == and should_not == (and some more) which are nothing more than syntactic sugar for saying that you expect one value to equal another value. Therefore, because this methods have been added to all objects and also because in Ruby really everything is an object – even a numeric value like the length of an array – we can call the method should == on the length of the array @choppedCucumbers.

The step definitions are quite silly (actually, the whole scenario is silly) and in this example we did not even test any production code – all the code is in the step file. But some of the basic concepts behind Cucumber should have become clear now.

Remark: We need to call the method to_i in two places because the argument that comes from the regex-match is always a string. We could get rid of this nuisance by using a step argument transformer if we wanted.

Fine, and now?

That should cover the basics of Cucumber, so let’s stop toying around with silly examples. Proceed to part two to test a real web application.

If you have questions or comments regarding the setup or the basics of Cucumber, please drop a line below.

|

share post

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.