In a previous post, I gave you some introductory information on Cucumber, a great framework for writing and executing high level descriptions of your software's functionality. In this post, I'll take a deeper dive and talk about a few more advanced Cucumber topics: project structures, multiple language support, scenario tables, free-form stories, tags, hooks and backgrounds. As always, for more detailed information see the documentation and/or The RSpec Book.
Let's start by taking a look at your project structure: the usual advice is to have a
features directory as the root of your Cucumber work. In that directory, you place all of your
.feature files which contain your features (as you would expect) as well as
support directory should contain whatever support code your features need, and an
env.rb file which is responsible for loading any required code that lives outside the
feature directory tree.
step_definitions directory, you place the files (with
.rb extensions) that contain your step definitions. These will all get loaded when your features run.
You can have multiple subdirectories in the
features directory for grouping features. This allows you to run the features in a particular directory. While this can be useful, it can be awkward in practice. That's because (as of this writing)
cucumber loads each ruby file (ending in
.rb) it finds in the directory you tell it to run and, recursively, all subdirectories. For example, consider the following tree (each
.rb files simply has a
puts "x" where
x is the name of the file):
So when you run
cucumber features, you get:
but, if you run
cucumber features/sub2 you get:
The issue is that features don't inherit support code or steps from parent directories unless that parent is also visited by Cucumber. So if you want to run subdirectories separately and they share setup code or steps, you have to somehow duplicate that code (possibly by explicitly requiring files from up the tree). This isn't significant, but it is a bit messy. A better approach might be to use tags (described below). If your features groups don't share much, then using subdirectories can work fine.
In the referenced previous post I talked briefly about Cucumber's multiple language support; here's I'll show you how it's done, and how you can add your own if required.
cucumber/lib/cucumber/languages.yml defines the 'natural language' support of Cucumber. This is a yaml file that provides multi-lingual aliases for Gherkin keywords. As an example, here's the entries for English, LOLZ, and Japanese:
To see what languages are available in the version of Cucumber you have, issue the following command:
cucumber --language help
If your desired language isn't supported, clone aslakhellesoy/cucumber and add it. If you do, please send aslakhellesoy a pull request to have it considered for inclusion.
To run features written in a specific language, use the
--language flag followed by the 2 (or so) letter abbreviation of the desired language. For example:
cucumber --language ja
Whenever I'm talking about Gherkin keywords, I'll use the English form. Everything said applies to any language version.
As mentioned in the intro post, following the feature header, you can have any amount of free-form text. This is generally used to give further background or explanation of the feature. Here's an example from The RSpec Book:
Scenario Outlines and Example Tables
Sometimes you will have a collection of scenarios that are structurally all the same, differing only in some set of values. It might be nice to represent these as a table. In fact, the FIT project is aimed at doing just this. However, it can be nice to use a single tool to solve a given class of problem, and so Cucumber supports this style of testing.
There are two pieces to this capability. First is the definition of the scenario outline. This is a skeleton of the scenario, written with placeholders for the actual values. Continuing on from the previous feature:
This is very similar to a regular scenario definition, with two exceptions. First, you use
Scenario Outline: instead of
Scenario:. This is what informs the system that you want to do a tabular style scenario. The second difference is the use of placeholders, e.g.
The second piece is a data table (or tables). These start with the
Examples: keyword. Following the keyword is a textual description of the data in the table. Then we have a table header that serves to map the columns to the placeholders in the scenario. Following that is the data for each case the scenario should be applied to, one per line. Table cells are bracketed by vertical bars (i.e
Any number of scenario tables can be used, and all apply to the most recent scenario outline. This results in far less copy/paste and the associated potential for error, as well as being a far more concise format.
In the simplest case, Cucumber runs all the scenarios in all the features that you point it at. By using tags you can be more specific about what is run. In my opinion, this is a killer feature. You tag features or scenarios by prefixing them with one or more tags, separated by spaces. A tag is simply an identifier prefixed by
@. For example:
To run features and/or scenarios with specific tags you use the
--tags command line flag and provide a comma separated list of tags. Specifying a feature with a tag runs all scenarios in the feature. Here's a couple of examples based on the above code:
You can also specify tags not to run, like so:
Cucumber provides the ability to supply hooks to modify the behavior of executing features. Hooks can be used at the various levels of granularity, generally before and after, and are often defined in your env.rb file. Hooks are executed whenever the event they are defined for occurs.
Global hooks run when Cucumber begins and exits. Begin hooks are informal: simply put the desired code in a file in the
features/support directory (possibly
env.rb, but I'd advise keeping it separate).
An exit hook is more formal, using
at_exit. Here's an example of a pair of global hooks:
You can add blocks that will run before and after each scenario:
The After block will be passed the scenario that just ran. You can use this to inspect its result status by using the
exception methods. For example:
Cucumber gives you the ability to define a block that will be executed after each (and every) step:
If you've tagged some of your scenarios, you can also tag scenario and step hooks. You simply pass the tags as arguments to the hook methods). These tagged hooks will only be executed before/after scenarios that are tagged the same and after steps in scenarios tagged the same. Here's an example where the hooks will only be run before scenarios tagged with
Background is very much like a scenario in that it consists of a series of steps. The difference is that its steps are executed before the steps of each scenario in the feature. It's basically a factoring out of a set of common lead-in steps for the features scenarios. One thing to remember is that a
Background is run after any
You'll generally only have given Backgrounds. Here's an example:
So that's a quick look at some of the more advanced features of Cucumber. It's a great tool, with a growing community behind and around it. I'll be posting on Cucumber Best Practices next time, so keep an eye out. Enjoy, and comment with any questions!