Behavior-driven development (BDD) is an Agile software development process encouraging collaboration between developers, QA and non-technical participants in a software project. Cucumber is an example of a JavaScript (JS) methodology that enables BDD. It helps to lay a test automation foundation by embracing coding standards and design patterns that will be used in the automation development process.
Cucumber is an open-source software testing tool that is written in Ruby. Cucumber enables the writing of test cases that helps them be easily understood. Cucumber can be thought of as a testing framework, driven by plain English text. It will serve as documentation, an automated test, and an aid to development.
Feature files in Cucumber help non-technical stakeholders engage with the testing that is being done. Feature files are often located at the root of the /features directory, and concentrate on a single business value. They may be grouped in a subfolder if they describe a common object. The grouped filenames can represent the action and context of the subfolder.
Background steps are run before a scenario. Scenarios run independently, and should lack dependencies on other scenarios. Declarative, rather than imperative, scenarios will better show the implementation details of the application. Additionally, tags on features are inherited by scenarios which reduces tag redundancy.
Scenarios may be initialized by using Given statements which put a system into a known state before user interaction. When statements describe a key action the user performs, and a Then statement observes the outcome. Avoid details in a scenario that are not required.
Reusable objects aid in the efficiency of Cucumber tests. Because objects interact, they need a careful design.
Step definitions (which Cucumber uses to translate plain-text Gherkin files into actions) are defined in the Ruby files under features/step_definitions/*_steps.rb
. Steps should be reusable by avoiding the incorporation of hard-coded parameters.
Step definitions can be reused in other step definitions by calling steps helper, but this use is not recommended. many_steps helper is a better method since it makes a meaningful stack trace possible should a test crash.
A superior reuse approach involves Page Object Pattern, which is mapped to a Ruby class file. Each method within the class represents a page object on the page. If UI coupling is centralized in the design of the objects, the project is more easily maintainable. It ends up with only one location where changes are needed rather than multiple places throughout the step definitions.
The rspec-expectations library is bundled with many built-in matchers for testing validation. Each of the matchers can be used with expect(..).to or expect(..).not_to
to define positive and negative expectations respectively on an object under test. Rspec-expectations matchers work better than Selenium matchers in a Ruby-based testing system like Cucumber.
Cucumber configuration requires certain files. Pinning the version of these files stops auto-updating which disrupts test automation.
cucumber.yml simplifies command-line execution by defining reusable profiles within the cucumber.yml file. Gemfile is used to manage dependencies for a Ruby project. Rakefile is a software task management and build automation tool. It allows the specification of tasks, description of dependencies, and group tasks in namespaces. support/env.rb
configures the environment of Cucumber. It simplifies changes in Cucumber’s execution environment such as local to remote.
The structure of a complete test would flow in this manner:
Write .feature
files with scenarios and a given-when-then structure for each scenario
Write step definition files where you define functions that match the steps in your scenarios
Implement these functions as you wish.
Run the tests by executing the cucumber-js executable in the node_modules/.bin
folder
A simple “hello world” Cucumber test will start with the feature description. Italicized text is entered to create the test.
# feature/hello_cucumber.feature
Feature: Hello Cucumber
I want a greeting to be shown
Scenario: User sees the welcome message
When I go to the homepage
Then I should see the welcome message
Scenario instructions are called steps, and they drive the tests.
For each step in the scenario, Ruby code will be executed. The step definitions (the blocks of code) are placed in a file called hello_steps.rb
. For this, it will contain:
When(/^I go to the homepage$/) do
visit root_path
endit
Then(/^I should see the welcome message$/) do
expect(page).to have_content("Hello Cucumber")
End
Each line in the Cucumber feature file, called a scenario step, is associated with its corresponding step definition. This matches the step definition string with the use of a regular expression.
Getting to the homepage means the user will visit the root_path
(which is standard Rails terminology, and it’s something defined in config/routes.rb file). In the expectation step, a check is made that the homepage contains the “Hello Cucumber” text.
# config/routes.rb
Rails.application.routes.draw do
root 'welcome#index
end
# app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
def index
end
end
# app/views/welcome/index.html.erb
Then running all of this provides:
$ cucumber -s
Using the default profile…
Feature: Hello Cucumber
Scenario: User sees the welcome message
When I go to the homepage
Then I should see the welcome message
1 scenario (1 passed)
2 steps (2 passed)
The -s
flag tells Cucumber to hide the location of each step definition, which is the default behavior. The example has all the basic Cucumber elements.
BDD (Cucumber) allows for team collaboration early in the planning and development phases. Keep the numbers down. Ask yourself and the team: “If I can write a low-level test for this user story, should I write an UI acceptance test?” We should automate only the things that need to be automated at the UI level. It is a known fact that low-level tests are more reliable compared to UI tests. Cucumber is code, and all types of programming require thoughts upfront before writing automated tests. It is so important to lay a test automation foundation that embraces coding standards and follows design patterns that everyone will follow during automation development.