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.
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).
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
setupin xUnit and
beforeblocks 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.
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.