Cucumber is a framework
for writing and executing high level descriptions of your software's
functionality. Call these tests, examples, specifications, whatever...
it doesn't matter too much. What I'm talking about has traditionally
been called functional, integration, and/or system tests. In XP terms
this includes tests called Story Tests, Customer Tests, and/or
Acceptance Tests.
One of Cucumber's most compelling features is that it provides the
ability to write these descriptions using plain text in your native
language. Cucumber's language, Gherkin, is usable in a growing variety
of human languages, including
LOLZ.
The advantage of this is that these feature descriptions can be written
and/or understood by non-technical people involved in the project.
One important thing to keep in mind is that Cucumber is NOT a
replacement for RSpec, test/unit, etc. It is not a low level
testing/specification framework.
Cucumber plays a central role in a development approach called
Behaviour Driven Development (BDD).
A Bit About BDD
Dan North describes BDD as “writing software that matters” in
The RSpec Book and outlines 3 principles:
- Enough is enough: do as much planning, analysis, and design as you need, but no more.
- Deliver stakeholder value: everything you do should deliver value or increase your ability to do so.
- It's a behavior: everyone involved should have the same way of talking about the system and what it does.
BDD in its grandest sense is about communication and viewing your
software as a system with behaviour. BDD tools such as RSpec and
Cucumber strive to enable you to describe the behavior of your software
in a very understandable way: understandable to everyone involved.
Features
A feature is something that your software does (or should do), and
generally corresponds to a user story and a way to tell when it's
finished and that it works.
The general format of a feature is:
Feature: <short description>
<story>
<scenario 1>
...
<scenario n>
Within the definition of a feature you can provide a plain text
description of the story. You can use any format for this, but having
some sort of template makes it easier to see the important bits of
information with a quick glance. Once of the more common template is
discussed by Mike Cohn in
User Stories Applied:
As a <role>
I want <feature>
so that <business value>
This format focuses us on three important questions:
- Who's using the system?
- What are they doing?
- Why do they care?
Here's a sample story:
As a code-breaker
I want to start a game
So that I can break the code
That defines who the user is
(a code-breaker), what they want to do
(start a game) and why
(be able to break the code).
Scenarios
A feature is defined by one or more scenarios. A scenario is a
sequence of steps through the feature that exercises one path. Cucumber
uses the BDD style that Dan North put forth with his jBehave project:
given-
when-
then.
A scenario is made up of 3 sections related to the 3 types of steps:
- Given: This sets up preconditions, or context, for the scenario. It works much like the
setup
in xUnit and before
blocks in RSpec.
- When: This is what the feature is talking about, the action, the behaviour that we're focused on.
- Then: This checks postconditions… it verifies that the right thing happen in the When stage.
The general form of a scenario is:
Scenario: <description>
<step 1>
…
<step n>
Following our example, here's a scenario:
Scenario: start game
Given I am not yet playing
When I start a new game
Then the game should say “Welcome to CodeBreaker”
And the game should say “Enter guess:”
One thing to note is the last line. Notice the
And.
And can be used in any of the three sections. It serves as a nice shorthand for repeating the
Given,
When, or
Then. And stands in for whatever the most recent explicitly named step was:
Given,
When, or
Then. The last two lines above could have been:
Then the game should say “Welcome to CodeBreaker”
Then the game should say “Enter guess:”
That doesn't read nearly as well, though. Similarly, if you want to state a negative
Then step, you can use
But in place of
Then, for example:
Then the game should say “Welcome to CodeBreaker”
But the game should not say “Save the cheerleader, save the world”
While there are no constraints on the number or order of steps, it is
generally best to keep scenarios as simple as possible and have
multiple simple scenarios. The closer your scenarios conform to the
given-
when-
then format, the better.
Implementing Steps
So here's our feature:
Feature: code-breaker starts game
As a code-breaker
I want to start a game
So that I can break the code
Scenario: start game
Given I am not yet playing
When I start a new game
Then the game should say “Welcome to CodeBreaker”
And the game should say “Enter guess:”
If you run that you'll see an echo of the feature followed by (using Cucumber 0.3.11):
1 scenario (1 undefined)
4 steps (4 undefined)
0m0.001s
You can implement step definitions for undefined steps with these snippets:
Given /^I am not yet playing$/ do
pending
end
When /^I start a new game$/ do
pending
end
Then /^the game should say “Welcome to CodeBreaker”$/ do
pending
end
Then /^the game should say “Enter guess:”$/ do
pending
end
The next step (pardon the pun) is to define what each of our steps
mean. When we executed our feature with no steps defined, Cucumber gave
us skeletons for the undefined steps (see above). If we put those in
the file features/step_definitions/codebreaker.rb and run it again, we
get:
1 scenario (1 pending)
4 steps (3 skipped, 1 pending)
As you can see from above, the basic structure of a step definition is the keyword (i.e.
Given,
When, or
Then)
followed by a regular expression and a block of Ruby code. The regular
expression is used to identify the desired step implementation. The
step text in scenarios is matched against the regular expressions of
appropriate (i.e.
given,
when, or
then) step
implementations to find the one to use. When a match is found, the
substrings matching any groups in the regular expression are used as
arguments to the block which is then evaluated. The result of that
evaluation is discarded. Note that these arguments are
always
strings. If you need them converted to other types, it's up to you to
do that conversion in the block. These groups and parameters have
knowledge of position (i.e.. group 1 is used as the first parameter,
group 2 as the second, and so on).
Notice that the suggested steps are completely concrete. While that
is fine some of the time, you will generally want to refactor steps and
make them more generic and reusable. Notice that there are two
then step definitions that are much the same:
Then /^the game should say “Welcome to CodeBreaker”$/ do
end
Then /^the game should say “Enter guess:”$/ do
end
The difference is the string inside quotes
(using quotes or similar delimiters is very handy for matching the groups). We can factor that out into a regex group:
Then /^the game should say “(.*)”$/ do |message|
end
Our
given step could be:
Given /^I am not yet playing$/ do
@game = Codebreaker::Game.new
end
Next is the
when step:
When /^I start a new game$/ do
end
This might be implemented as:
When /^I start a new game$/ do
@messages = @game.start
end
Now we have the variable
@messages
that can be used in subsequent steps.
Recall how we left our
then step definition:
Then /^the game should say “(.*)”$/ do |message|
end
We can now fill this in:
Then /^the game should say “(.*)”$/ do |message|
@messages.should include(message)
end
You can use whatever method of verification you want in the
then steps: rspec, test/unit, shoulda, etc. I happen to prefer RSpec.
Earlier I mentioned that Cucumber supports various languages including LOLZ. Here's a feature in LOLZ:
OH HAI: STUFFING
MISHUN: CUCUMBR
I CAN HAZ IN TEH BEGINNIN 3 CUCUMBRZ
WEN I EAT 2 CUCUMBRZ
DEN I HAZ 2 CUCUMBERZ IN MAH BELLY
AN IN TEH END 1 CUCUMBRZ KTHXBAI
And the step implementations:
ICANHAZ /^IN TEH BEGINNIN (d+) CUCUMBRZ$/ do |n|
@basket = Basket.new(n.to_i)
end
WEN /^I EAT (d+) CUCUMBRZ$/ do |n|
@belly = Belly.new
@belly.eat(@basket.take(n.to_i))
end
DEN /^I HAZ (d+) CUCUMBERZ IN MAH BELLY$/ do |n|
@belly.cukes.should == n.to_i
end
DEN /^IN TEH END (d+) CUCUMBRZ KTHXBAI$/ do |n|
@basket.cukes.should == n.to_i
end
And that's all there is to understanding the basics of Cucumber.