Cucumber Features
In this chapter we’ll go through the implemented Cucumber features in CWT Cucumber.
Scenario
A Scenario is a test, which executes all steps (given, when, then). Writing a Scenario is pretty straightforward. Define all steps to execute in your step definition:
GIVEN(init_box, "An empty box")
{
const box& my_box = cuke::context<box>();
cuke::equal(my_box.items_count(), 0);
}
WHEN(add_item, "I place {int} x {string} in it")
{
const std::size_t count = CUKE_ARG(1);
const std::string item = CUKE_ARG(2);
cuke::context<box>().add_items(item, count);
}
THEN(check_box_size, "The box contains {int} item(s)")
{
const int items_count = CUKE_ARG(1);
const box& my_box = cuke::context<box>();
cuke::equal(my_box.items_count(), items_count);
}
Feature: My first feature
This is my cucumber-cpp hello world
Scenario: First Scenario
Given An empty box
When I place 2 x "apple" in it
Then The box contains 2 item(s)
Scenario Outline
In a Scenario Outline you can define variables. The Scenario will then run for each line in the given table, with the given values. Instead of values, place variables into your step in the feature file:
Feature: My first feature
This is my cwt-cucumber hello world
Scenario Outline: First Scenario Outline
Given An empty box
When I place <count> x <item> in it
Then The box contains <count> item(s)
Examples: Fruits
| count | item |
| 1 | "apple" |
| 2 | "bananas" |
Hooks
Hooks are executed before and after each scenario or step. Implementation is fairly straightforward. You can have multiple hooks of the same type. They will all be executed at the appropriate time.
BEFORE(before)
{
// this runs before every scenario
}
AFTER(after)
{
// this runs after every scenario
}
BEFORE_STEP(before_step)
{
// this runs before every step
}
AFTER_STEP(after_step)
{
// this runs after every step
}
You can try it out, and add some prints to it.
Tagged Hooks
ou can add a tag expression to your hook. You use these defines:
BEFORE_T(name, "tags come here")
for a tagged hook before a scenrioAFTER_T(name, "tags come here")
for a tagged hook after a scenario
This means that a tagged hook is executed when a scenario satisfies the specified condition. You can pass any logical expression to a tagged hook:
AFTER_T(dispatch_box, "@ship or @important")
{
std::cout << "The box is shipped!" << std::endl;
}
Note
You can access the cuke::context
exactly like in a step.
Feature: Scenarios with tags
@ship
Scenario: We want to ship cucumbers
Given An empty box
When I place 1 x "cucumber" in it
Then The box contains 1 item(s)
@important
Scenario: Important items must be shipped immediately
Given An empty box
When I place 2 x "important items" in it
Then The box contains 2 item(s)
And now we can see that our box was shipped:
Feature: Scenarios with tags ./examples/features/5_tagged_hooks.feature:1
Scenario: We want to ship cucumbers ./examples/features/5_tagged_hooks.feature:4
[ PASSED ] An empty box ./examples/features/5_tagged_hooks.feature:5
[ PASSED ] I place 1 x "cucumber" in it ./examples/features/5_tagged_hooks.feature:6
[ PASSED ] The box contains 1 item(s) ./examples/features/5_tagged_hooks.feature:7
The box is shipped!
Scenario: Important items must be shipped immediately ./examples/features/5_tagged_hooks.feature:10
[ PASSED ] An empty box ./examples/features/5_tagged_hooks.feature:11
[ PASSED ] I place 2 x "important items" in it ./examples/features/5_tagged_hooks.feature:12
[ PASSED ] The box contains 2 item(s) ./examples/features/5_tagged_hooks.feature:13
The box is shipped!
2 Scenarios (2 passed)
6 Steps (6 passed)
Background
A background is a set of steps (or a single step) which are the first steps of every Scenario in a Feature. After the feature definition add Background
, see ./examples/features/3_background.feature
:
Feature: We always need apples!
Background: Add an apple
Given An empty box
When I place 1 x "apple" in it
Scenario: Apples Apples Apples
When I place 1 x "apple" in it
Then The box contains 2 item(s)
Scenario: Apples and Bananas
When I place 1 x "apple" in it
And I place 1 x "banana" in it
Then The box contains 3 item(s)
In this case every Scenario starts with a box and one apple in it.
Doc Strings
Doc strings can be appended to a step. There is no parameter in the step definition needed.
To access a doc string use CUKE_DOC_STRING()
:
// There is no parameter needed in your step
WHEN(doc_string, "There is a doc string:")
{
// and now you can use it here:
const std::string& str = CUKE_DOC_STRING();
// ..
}
And this allows a doc string in a Feature file:
Feature: This is a doc string example
Scenario: Doc string with quotes
When There is a doc string:
"""
This is a docstring with quotes
after a step
"""
Scenario: Doc string with backticks
When There is a doc string:
```
This is a docstring with backticks
after a step
```
Note
The doc string is always the last argument passed to the step. Alternatively you can also use in this case: const std::string& str = CUKE_ARG(1);
Tables / Datatables
Similar to doc strings, you can append tables to a defined step. Then there are three different options to access the values. To create a table in your step definition use:
- const cuke::table& t = CUKE_TABLE();
or as copy
- cuke::table t = CUKE_TABLE();
You can directly access the elements with the operator[]
. But the underlying value is a cuke::value
which you have to cast accordingly with as
or to _string
.
Note
cuke::value
uses type erasure and is the underlying type for all values for this interpreter. You have always to cast it to whatever type you expect. If this cast does not work (type missmatch) it throws an exception.
const cuke::table& t = CUKE_TABLE();
t[0][0].to_string();
t[0][1].as<int>();
// ...
Warning
Note that all floating point values in a table are doubles
. You cannot cast them to a float; if you need them as a float, go the extra mile and create a double
, which you then cast to a float
.
Option 1: Raw Access
First we look at a raw table. This means there is no header line or identifiers for the given values:
Scenario: Adding items with raw
Given An empty box
When I add all items with raw():
| apple | 2 |
| strawberry | 3 |
| banana | 5 |
Then The box contains 10 item(s)
You can iterate over this table with raw()
:
WHEN(add_table_raw, "I add all items with raw():")
{
// create a table
const cuke::table& t = CUKE_TABLE();
// with raw() you iterate over all rows
for (const auto& row : t.raw())
{
// and with the operator[] you get access to each cell in each row
cuke::context<box>().add_items(row[0].to_string(), row[1].copy_as<long>());
}
}
Option 2: Hashes
With an additional header in the table we can make this table more descriptive:
Scenario: Adding items with hashes
Given An empty box
When I add all items with hashes():
| ITEM | QUANTITY |
| apple | 3 |
| banana | 6 |
Then The box contains 9 item(s)
You can now iterate over the table using hashes()
and access the elements with string literals:
WHEN(add_table_hashes, "I add all items with hashes():")
{
const cuke::table& t = CUKE_TABLE();
for (const auto& row : t.hashes())
{
cuke::context<box>().add_items(row["ITEM"].to_string(), row["QUANTITY"].as<long>());
}
}
Option 3: Key/Value Pairs or Rows Hash
Another more descriptive way works for key value pairs, or rows hash. The first column describes the element, the second holds the element:
Scenario: Adding items with rows_hash
Given An empty box
When I add the following item with rows_hash():
| ITEM | really good apples |
| QUANTITY | 3 |
Then The box contains 3 item(s)
And with cuke::table::pair hash_rows = t.rows_hash();
you can create this hash map. The access to each element is again by the string literal.
Note
cuke::table::pair
is just an alias for a std::unordered_map<std::string, cuke::value>
.
WHEN(add_table_rows_hash, "I add the following item with rows_hash():")
{
const cuke::table& t = CUKE_TABLE();
cuke::table::pair hash_rows = t.rows_hash();
cuke::context<box>().add_items(hash_rows["ITEM"].to_string(), hash_rows["QUANTITY"].as<long>());
}
Executing Single Scenarios / Directories
If you only want to run single scenarios, you can append the appropriate line to the feature file:
This runs a Scenario in Line 6:
./build/bin/box ./examples/features/box.feature:6
This runs each Scenario in line 6, 11, 14:
./build/bin/box ./examples/features/box.feature:6:11:14
If you want to execute all feature files in a directory (and subdirectory), just pass the directory as argument:
./build/bin/box ./examples/features
Whats Missing
So, work is not done yet. There are still cucumber features, which are missing:
DataTables
Rules
Anything else is missing? Or found a Bug? Don’t hesitate and open an Issue. I’ll see whenever is time to continue implemeting stuff here.