Back to Testing guides

Testing with Cucumber

Stanley Ulili
Updated on September 23, 2025

Testing often feels like a barrier between developers and stakeholders, creating confusion about what software should actually do. Cucumber bridges this gap by using plain English specifications that both technical and non-technical team members can understand and contribute to.

Cucumber transforms requirements into executable tests using Gherkin syntax, a structured language that reads like natural conversation. This approach ensures everyone shares the same understanding of how your application should behave, reducing miscommunication and missed requirements.

This guide will walk you through setting up Cucumber from scratch, writing your first scenarios, and building a complete behavior-driven development workflow.

Prerequisites

To follow this guide, you'll need Ruby 3.0 or later installed:

 
ruby --version
Output
ruby 3.4.5

This guide assumes familiarity with Ruby basics including methods, classes, and object-oriented programming. You should also understand fundamental testing concepts, though no prior experience with behavior-driven development is required.

Setting up the project

In this section, you’ll explore Cucumber with hands-on examples that bring behavior-driven development to life. A dedicated project environment lets you safely experiment with its syntax and workflow without impacting production code.

Create a project directory for Cucumber exploration:

 
mkdir cucumber-testing-guide && cd cucumber-testing-guide

Initialize a Gemfile for dependency management:

 
bundle init

Add Cucumber and supporting gems to your Gemfile:

Gemfile
source "https://rubygems.org"

gem 'cucumber', '~> 10.1'
gem 'rspec-expectations', '~> 3.13'

Install the dependencies:

 
bundle install
Output
Fetching gem metadata from https://rubygems.org/........
Resolving dependencies...
...
Fetching cucumber 10.1.0
Installing cucumber 10.1.0
Bundle complete! 2 Gemfile dependencies, 21 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
1 installed gem you directly depend on is looking for funding.
  Run `bundle fund` for details

Initialize the Cucumber directory structure:

 
bundle exec cucumber --init
Output
  create   features
  create   features/step_definitions
  create   features/support
  create   features/support/env.rb

This creates a standard Cucumber project structure with directories for features, step definitions, and support files. The env.rb file contains configuration that loads before any features run.

Your Cucumber environment is now ready for behavior-driven development exploration.

Understanding the behavior-driven development challenge

Traditional testing focuses on implementation details rather than user behavior, creating tests that break when code changes even if the functionality remains correct. This approach makes it difficult for stakeholders to understand what the software actually does.

Consider a simple login system. Traditional unit tests might verify that a User class has an authenticate method:

 
def test_authenticate_method_exists
  user = User.new
  assert user.respond_to?(:authenticate)
end

This test tells us nothing about how authentication should work from a user's perspective. What happens when credentials are correct? What about invalid passwords? Should the system lock accounts after failed attempts?

Behavior-driven development addresses this by focusing on user scenarios instead of code implementation. Rather than testing methods exist, you describe what users should experience:

  • When a user enters correct credentials, they should be logged in
  • When a user enters wrong credentials, they should see an error message
  • When a user fails to log in multiple times, their account should be temporarily locked

These scenarios become executable tests that verify your application works correctly from the user's perspective, regardless of how the underlying code is structured.

Writing your first Cucumber scenario

Cucumber uses Gherkin syntax to write scenarios that read like natural language. Each scenario describes a specific user interaction with your application using Given-When-Then steps.

Create your first feature file in the features direcotry to describe basic calculator functionality:

features/calculator.feature
Feature: Basic Calculator Operations
  As a user
  I want to perform basic arithmetic operations
  So that I can calculate results quickly

  Scenario: Adding two positive numbers
    Given I have a calculator
    When I add 5 and 3
    Then the result should be 8

Gherkin scenarios follow a specific structure that makes them easy to understand:

  • Feature describes the overall functionality being tested
  • As/I want/So that explains who benefits and why this matters
  • Scenario defines a specific situation or use case
  • Given sets up the initial context
  • When describes the action being performed
  • Then specifies the expected outcome

The beauty of Gherkin lies in its readability. Anyone on your team can understand what this scenario tests without knowing how calculators are implemented.

Run Cucumber to see what happens with undefined steps:

 
bundle exec cucumber
Output
Feature: Basic Calculator Operations
  As a user
  I want to perform basic arithmetic operations
  So that I can calculate results quickly

  Scenario: Adding two positive numbers # features/calculator.feature:6
    Given I have a calculator           # features/calculator.feature:7
    When I add 5 and 3                  # features/calculator.feature:8
    Then the result should be 8         # features/calculator.feature:9

1 scenario (1 undefined)
3 steps (3 undefined)
0m0.015s

You can implement step definitions for undefined steps with these snippets:

Given('I have a calculator') do
  pending # Write code here that turns the phrase above into concrete actions
end

When('I add {int} and {int}') do |int, int2|
# When('I add {int} and {float}') do |int, float|
# When('I add {float} and {int}') do |float, int|
# When('I add {float} and {float}') do |float, float2|
  pending # Write code here that turns the phrase above into concrete actions
end

Then('the result should be {int}') do |int|
# Then('the result should be {float}') do |float|
  pending # Write code here that turns the phrase above into concrete actions
end
...

Cucumber automatically identifies undefined steps and suggests Ruby code snippets to implement them. Notice how it recognizes numbers in the steps and converts them to parameters.

Implementing step definitions

Step definitions connect Gherkin scenarios to Ruby code that actually performs the described actions. Each step definition uses pattern matching to capture parameters from scenario text.

Create step definitions for the calculator scenario:

features/step_definitions/calculator_steps.rb
Given('I have a calculator') do
  @calculator = Calculator.new
end

When('I add {int} and {int}') do |first_number, second_number|
  @result = @calculator.add(first_number, second_number)
end

Then('the result should be {int}') do |expected_result|
  expect(@result).to eq(expected_result)
end

Step definitions use instance variables to share data between steps within the same scenario. The @calculator created in the Given step remains available in subsequent When and Then steps.

Cucumber automatically converts {int} parameters to Ruby integers, eliminating the need for manual string-to-number conversion. This parameter extraction makes steps reusable across different scenarios with various values.

Create the Calculator class to support these step definitions:

lib/calculator.rb
class Calculator
  def add(a, b)
    a + b
  end
end

Update the support file to load your application code:

features/support/env.rb
require_relative '../../lib/calculator'
require 'rspec/expectations'

The env.rb file loads before any features run, making it the perfect place to require application files and configure testing libraries like RSpec expectations.

Run the scenario again:

 
bundle exec cucumber
Output
Feature: Basic Calculator Operations
  As a user
  I want to perform basic arithmetic operations
  So that I can calculate results quickly

  Scenario: Adding two positive numbers # features/calculator.feature:6
    Given I have a calculator           # features/step_definitions/calculator_steps.rb:1
    When I add 5 and 3                  # features/step_definitions/calculator_steps.rb:5
    Then the result should be 8         # features/step_definitions/calculator_steps.rb:9

1 scenario (1 passed)
3 steps (3 passed)
0m0.001s

The scenario now passes completely, demonstrating how Cucumber connects human-readable specifications to executable code that verifies your application works correctly. you can also see colors... exploian about colors

Cucumber provides color-coded output that makes test results immediately clear. Passing steps appear in green, while failing steps show up in red, and pending or undefined steps display in yellow. This visual feedback lets you quickly scan results and identify which scenarios need attention without reading through detailed text output.

Screenshot showing Cucumber's colorized terminal output with green text for passing scenarios

The color coding becomes especially valuable when running large test suites where dozens of scenarios execute at once. You can immediately spot problems by looking for red text, while green confirms everything works as expected. This visual approach reduces the cognitive load of interpreting test results and helps maintain focus during development cycles.

Building comprehensive scenario coverage

Real applications require testing multiple scenarios to ensure they handle various user interactions correctly. Cucumber's scenario structure makes it easy to describe different situations systematically.

Expand the feature file to cover additional calculator operations:

features/calculator.feature
Feature: Basic Calculator Operations
  As a user
  I want to perform basic arithmetic operations
  So that I can calculate results quickly

  Scenario: Adding two positive numbers
    Given I have a calculator
    When I add 5 and 3
    Then the result should be 8

Scenario: Adding negative numbers
Given I have a calculator
When I add -2 and -3
Then the result should be -5
Scenario: Subtracting numbers
Given I have a calculator
When I subtract 7 from 10
Then the result should be 3
Scenario: Multiplying numbers
Given I have a calculator
When I multiply 4 and 6
Then the result should be 24
Scenario: Dividing numbers
Given I have a calculator
When I divide 15 by 3
Then the result should be 5

Each scenario tests a specific operation while following the same Given-When-Then structure. This consistency makes scenarios easy to read and understand, even as the feature file grows larger.

Add corresponding step definitions for the new operations:

features/step_definitions/calculator_steps.rb
Given('I have a calculator') do
  @calculator = Calculator.new
end

When('I add {int} and {int}') do |first_number, second_number|
  @result = @calculator.add(first_number, second_number)
end

When('I subtract {int} from {int}') do |subtrahend, minuend|
@result = @calculator.subtract(minuend, subtrahend)
end
When('I multiply {int} and {int}') do |first_number, second_number|
@result = @calculator.multiply(first_number, second_number)
end
When('I divide {int} by {int}') do |dividend, divisor|
@result = @calculator.divide(dividend, divisor)
end
Then('the result should be {int}') do |expected_result| expect(@result).to eq(expected_result) end

Notice how the Given and Then steps remain unchanged, demonstrating how well-designed step definitions can be reused across multiple scenarios. Only the When steps need to be added for new operations.

Update the Calculator class to support all operations:

lib/calculator.rb
class Calculator
  def add(a, b)
    a + b
  end

def subtract(a, b)
a - b
end
def multiply(a, b)
a * b
end
def divide(a, b)
a / b
end
end

Run the expanded test suite:

 
bundle exec cucumber
Output
Feature: Basic Calculator Operations
  As a user
  I want to perform basic arithmetic operations
  So that I can calculate results quickly

  Scenario: Adding two positive numbers # features/calculator.feature:6
    Given I have a calculator           # features/step_definitions/calculator_steps.rb:1
    When I add 5 and 3                  # features/step_definitions/calculator_steps.rb:5
    Then the result should be 8         # features/step_definitions/calculator_steps.rb:21

  Scenario: Adding negative numbers # features/calculator.feature:10
    Given I have a calculator       # features/step_definitions/calculator_steps.rb:1
    When I add -2 and -3            # features/step_definitions/calculator_steps.rb:5
    Then the result should be -5    # features/step_definitions/calculator_steps.rb:21

  Scenario: Subtracting numbers # features/calculator.feature:15
    Given I have a calculator   # features/step_definitions/calculator_steps.rb:1
    When I subtract 7 from 10   # features/step_definitions/calculator_steps.rb:9
    Then the result should be 3 # features/step_definitions/calculator_steps.rb:21

  Scenario: Multiplying numbers  # features/calculator.feature:20
    Given I have a calculator    # features/step_definitions/calculator_steps.rb:1
    When I multiply 4 and 6      # features/step_definitions/calculator_steps.rb:13
    Then the result should be 24 # features/step_definitions/calculator_steps.rb:21

  Scenario: Dividing numbers    # features/calculator.feature:25
    Given I have a calculator   # features/step_definitions/calculator_steps.rb:1
    When I divide 15 by 3       # features/step_definitions/calculator_steps.rb:17
    Then the result should be 5 # features/step_definitions/calculator_steps.rb:21

5 scenarios (5 passed)
15 steps (15 passed)
0m0.005s

All scenarios pass, demonstrating how Cucumber scenarios can efficiently cover multiple aspects of your application's behavior while remaining readable and maintainable.

Using scenario outlines for data-driven tests

When you need to test the same behavior with different data sets, Scenario Outlines eliminate repetition by parameterizing scenarios with example tables.

Create a feature that tests multiple calculations with various inputs:

features/multiplication_table.feature
Feature: Multiplication Table
  As a student
  I want to verify multiplication results
  So that I can check my homework answers

  Scenario Outline: Multiplying different numbers
    Given I have a calculator
    When I multiply <first_number> and <second_number>
    Then the result should be <expected_result>

    Examples:
      | first_number | second_number | expected_result |
      | 2            | 3             | 6              |
      | 4            | 5             | 20             |
      | 7            | 8             | 56             |
      | 9            | 9             | 81             |
      | 12           | 11            | 132            |

Scenario Outlines use placeholders in angle brackets that get replaced with values from the Examples table. Cucumber runs the scenario once for each row in the table, creating multiple test cases from a single scenario definition.

The Examples table makes it easy to add new test cases without writing additional scenarios. You can also include edge cases, boundary values, or regression test data in the same table.

Run the multiplication table feature:

 
bundle exec cucumber features/multiplication_table.feature
Output
Feature: Multiplication Table
  As a student
  I want to verify multiplication results
  So that I can check my homework answers

  Scenario Outline: Multiplying different numbers      # features/multiplication_table.feature:6
    Given I have a calculator                          # features/multiplication_table.feature:7
    When I multiply <first_number> and <second_number> # features/multiplication_table.feature:8
    Then the result should be <expected_result>        # features/multiplication_table.feature:9

    Examples: 
      | first_number | second_number | expected_result |
      | 2            | 3             | 6              |
      | 4            | 5             | 20             |
      | 7            | 8             | 56             |
      | 9            | 9             | 81             |
      | 12           | 11            | 132            |

5 scenarios (5 passed)
15 steps (15 passed)
0m0.005s

Notice how the Examples table displays clearly in the terminal output with proper column alignment. Each row represents a separate test case that Cucumber executes automatically. The table format makes it easy to see the relationship between input values and expected results at a glance.

Screenshot showing Cucumber's scenario outline execution with the Examples table clearly visible and all scenarios passing in green

The terminal preserves the table structure from your feature file, maintaining readability even in text-based output. This makes it simple to verify which specific data combinations were tested and identify patterns in your test data.

Cucumber automatically generates five separate scenarios from the single Scenario Outline, each with different parameter values. This approach makes it easy to test edge cases and verify your application handles various inputs correctly.

Running and filtering scenarios

Cucumber provides various options for running specific scenarios during development, making it easy to focus on particular functionality.

Running specific features and scenarios

Run individual feature files:

 
bundle exec cucumber features/calculator.feature

Run specific scenarios by line number:

 
bundle exec cucumber features/calculator.feature:16

This runs only the scenario that begins on line 16, useful when debugging specific test failures.

Final thoughts

In this article, you learned how Cucumber turns requirements into living documentation that you can understand. When you write scenarios in plain language, you align everyone around the same expectations and ensure your application behaves as intended.

With features like scenario outlines and color-coded output, Cucumber makes behavior-driven development both practical and collaborative. To dive deeper, check out the official Cucumber documentation.

Got an article suggestion? Let us know
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.