CWT Cucumber
So let us build the project and find all the implemented features.
Examples Build
Run a CMake build to build the examples:
cmake -S . -B ./build
cmake --build ./build
Conan Recipe for cwt-cucumber
I recommend using Conan for dependency management. I have a conan recipe in another GitHub repository to create the conan package. This will do the job for now (I’ll add instructions, once I’m done with the recipe). Later I can push it to conancenter, when this project is actually in use.
First we create the package:
git clone https://github.com/ThoSe1990/cwt-cucumber-conan.git
cd package
conan create . --version 1.1.0 --user cwt --channel stable
Then we can build the examples:
cd ../consumer
conan install . -of ./build
cmake -S . -B ./build -DCMAKE_TOOLCHAIN_FILE=./build/conan_toolchain.cmake
cmake --build ./build
And we’re done. If you want to use cwt-cucumber in your projects, you have to get the Conan package you just created and use it accordingly.
See ./conanfile.txt
:
[requires]
cucumber/1.1.0@cwt/stable
[generators]
CMakeToolchain
CMakeDeps
And CMakeLists.txt
:
find_package(cucumber REQUIRED)
set(target box)
add_executable(${target}
${CMAKE_CURRENT_SOURCE_DIR}/step_definition/step_definition.cpp
${CMAKE_CURRENT_SOURCE_DIR}/step_definition/hooks.cpp
)
target_link_libraries(${target} cucumber::cucumber)
Box Example
The example which I provided is a simple box
class:
class box
{
public:
box() = default;
void add_item(const std::string& item) { m_items.push_back(item); }
[[nodiscard]] std::size_t items_count() const noexcept
{
return m_items.size();
}
void close() noexcept { m_is_open = false; }
private:
bool m_is_open{true};
std::vector<std::string> m_items;
};
I think it’s pretty self-explanatory. A simple container to store some arbitrary items.
Implementing Steps
To implement steps, there are four different defines available. Each step creates a free function, which means we have to give it a function name. I didn’t want to use the __LINE__
macro or something like that, because that would mean that if we use multiple files, we have the same names.
STEP(function_name, "step definition goes here")
GIVEN(function_name, "step definition goes here")
WHEN(function_name, "step definition goes here")
THEN(function_name, "step definition goes here")
There is no difference between all these macros. The only reason for naming them is to better structure the code.
Accessing Values
Use Cucumber expression in your step definition in order to use values. In the code you can use CUKE_ARG(..)
to access the values by index. The index starts at 1 from the left:
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);
// ..
}
THEN(check_box_size, "The box contains {int} item(s)")
{
const int items_count = CUKE_ARG(1);
// ...
}
Note
I overloaded the implicit conversion operator to get different types. So the auto
keyword will not work here. And, using the correct types, cwt-cucumber checks at runtime if it can convert a value to each specific type.
Currently supported: {byte}
, {short}
, {int}
, {long}
, {float}
, {double}
and {string}
.
Scenario Context cuke::context
Use cuke::context
to store objects for the duration of a scenario. Each type can be added to cuke::context
once and lives as long as the scenario runs. At the end of each scenario, the cuke::context
destroys all objects.
cuke::context
can be called with or without arguments. If arguments are passed, they are passed to the constructor of the object. If arguments are given, the default constructor is called. Both calls return a reference to the given object:
// forwards 1,2,3 to your object:
cuke::context<some_object>(1,2,3);
// access or default initialize your object:
cuke::context<some_object>();
And in terms of the box
example we have for instance:
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);
for ([[maybe_unused]] int i = 0; i < count; i++)
{
cuke::context<box>().add_item(item);
}
}
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);
}
After a Scenario is done, the box
is destroyed.
The underlying mechanism is a type erased value context_value
, in a std::unordered_map<std::type_index, context_type>
.
Step Results
There are four differnt kinds of step results:
passed
failed
skipped
undefined
To evaluate a step, use the evaluation functions as in other test frameworks:
cuke::equal(lhs, rhs)
cuke::not_equal(lhs, rhs)
cuke::greater(lhs, rhs)
cuke::greater_or_equal(lhs, rhs)
cuke::less(lhs, rhs)
cuke::less_or_equal(lhs, rhs)
cuke::is_true(condition)
cuke::is_false(condition)
After the failed step, the rest is skipped. We can force a scenario to fail in this way:
Feature: My first feature .\examples\features\1_first_scenario.feature:2
Scenario: First Scenario .\examples\features\1_first_scenario.feature:5
[ PASSED ] An empty box .\examples\features\1_first_scenario.feature:6
[ PASSED ] I place 2 x "apple" in it .\examples\features\1_first_scenario.feature:7
Value 2 is not equal to 4 in following step:
[ FAILED ] The box contains 4 item(s) .\examples\features\1_first_scenario.feature:8
Failed Scenarios:
.\examples\features\1_first_scenario.feature:5
1 Scenarios (1 failed)
3 Steps (2 passed, 1 failed)
Executing Single Scenarios / Directories
### 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.