Introduction to BDD & Cucumber
What is BDD, setup, first feature file & runner
What is BDD?
Imagine you’re building a login page. The developer thinks “valid credentials” means any non-empty string. The tester thinks it means an email and password from the database. The product owner thinks it means an active user with a verified email. Three people, three different interpretations — and the bug is born before a single line of code is written.
Behavior-Driven Development (BDD) solves this by making everyone — business, development, and QA — describe the expected behavior in a shared, plain-English format before coding starts.
The Three Amigos
BDD works best when three perspectives come together in a conversation called the Three Amigos meeting:
| Role | Brings | Asks |
|---|---|---|
| Product Owner | Business rules & acceptance criteria | ”What should the system do?” |
| Developer | Technical feasibility & edge cases | ”What could go wrong technically?” |
| Tester (QA) | Scenarios, edge cases & what-ifs | ”What if the user does X instead?” |
The output of this meeting is a set of scenarios written in a structured format that everyone agrees on. These scenarios become both the specification and the test.
TDD vs BDD vs ATDD
You’ll hear these terms used interchangeably, but they’re different:
| Aspect | TDD | BDD | ATDD |
|---|---|---|---|
| Full Name | Test-Driven Development | Behavior-Driven Development | Acceptance Test-Driven Development |
| Focus | Unit-level code correctness | System behavior from user perspective | Acceptance criteria for user stories |
| Written By | Developers | Three Amigos (PO + Dev + QA) | QA + PO |
| Language | Programming language (Java, Python) | Plain English (Gherkin) | Plain English or structured |
| Tool Example | JUnit, TestNG | Cucumber, SpecFlow | FitNesse, Cucumber |
| Cycle | Red → Green → Refactor | Discover → Formulate → Automate | Discuss → Distill → Develop → Demo |
Why Cucumber?
Cucumber is the most popular BDD framework. It takes plain-English scenarios written in Gherkin syntax and connects them to automation code (Java, JavaScript, Ruby, etc.) through step definitions.
How Cucumber Works — The Big Picture
Here’s the flow from requirement to automated test:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Feature File │────▶│ Step Definitions │────▶│ Automation Code │
│ (Gherkin) │ │ (Java Glue) │ │ (Selenium, API) │
│ │ │ │ │ │
│ Given I login │ │ @Given("I login") │ │ driver.get(url) │
│ When I search │ │ @When("I search") │ │ page.search() │
│ Then I see... │ │ @Then("I see...") │ │ assert(result) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
│ ┌──────────────────┐ │
└─────────────▶│ Test Runner │◀─────────────┘
│ (TestNG/JUnit) │
│ Executes all │
└──────────────────┘
- Feature File — Plain-English scenarios in Gherkin format (
.feature) - Step Definitions — Java methods that map Gherkin steps to code
- Automation Code — The actual actions (click buttons, call APIs, assert results)
- Test Runner — Ties everything together and executes the tests
Gherkin at a Glance
Gherkin is the language Cucumber uses. It’s designed to be readable by non-technical people while being structured enough for machines to parse.
Your First Feature File
Feature: User Login
As a registered user
I want to log in to my account
So that I can access my personalized dashboard
Scenario: Successful login with valid credentials
Given I am on the login page
When I enter username "john_doe"
And I enter password "SecurePass123"
And I click the login button
Then I should be redirected to the dashboard
And I should see a welcome message "Hello, John!"
Let’s break this down:
| Keyword | Purpose | In This Example |
|---|---|---|
Feature | Names the feature being tested | User Login |
As a / I want / So that | User story (optional but helpful) | Context for why this matters |
Scenario | One specific test case | Successful login with valid credentials |
Given | Precondition — the starting state | I am on the login page |
When | Action — what the user does | I enter username, password, click login |
Then | Outcome — the expected result | Redirected to dashboard, see welcome message |
And | Continuation of the previous keyword | Additional steps within Given/When/Then |
Project Setup
Prerequisites
- Java 11+ installed (
java -versionto check) - Maven installed (
mvn -versionto check) - An IDE — IntelliJ IDEA (recommended) or Eclipse
- Cucumber plugin for your IDE (for syntax highlighting)
Step 1: Create a Maven Project
mvn archetype:generate \
-DgroupId=com.gyancode.cucumber \
-DartifactId=cucumber-bdd-tutorial \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
Step 2: Add Dependencies to pom.xml
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<cucumber.version>7.15.0</cucumber.version>
<selenium.version>4.16.0</selenium.version>
</properties>
<dependencies>
<!-- Cucumber Core -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
</dependency>
<!-- Cucumber + TestNG Integration -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>${cucumber.version}</version>
</dependency>
<!-- Selenium WebDriver -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<!-- TestNG -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.9.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<includes>
<include>**/TestRunner.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
Step 3: Project Structure
cucumber-bdd-tutorial/
├── pom.xml
└── src/
└── test/
├── java/
│ └── com/gyancode/cucumber/
│ ├── runner/
│ │ └── TestRunner.java
│ └── stepdefinitions/
│ └── LoginSteps.java
└── resources/
└── features/
└── login.feature
Your First Cucumber Test
Let’s write a complete working example — a simple calculator test that doesn’t need Selenium, so you can focus purely on how Cucumber works.
Step 1: The Feature File
Create src/test/resources/features/calculator.feature:
Feature: Calculator
As a user
I want to perform basic arithmetic
So that I can get correct results
Scenario: Add two numbers
Given I have a calculator
When I add 5 and 3
Then the result should be 8
Scenario: Subtract two numbers
Given I have a calculator
When I subtract 3 from 10
Then the result should be 7
Step 2: The Step Definitions
Create src/test/java/com/gyancode/cucumber/stepdefinitions/CalculatorSteps.java:
package com.gyancode.cucumber.stepdefinitions;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;
import org.testng.Assert;
public class CalculatorSteps {
private int result;
@Given("I have a calculator")
public void iHaveACalculator() {
// Setup — nothing needed for a simple calculator
System.out.println("Calculator is ready!");
}
@When("I add {int} and {int}")
public void iAddAnd(int a, int b) {
result = a + b;
}
@When("I subtract {int} from {int}")
public void iSubtractFrom(int a, int b) {
result = b - a;
}
@Then("the result should be {int}")
public void theResultShouldBe(int expected) {
Assert.assertEquals(result, expected,
"Expected " + expected + " but got " + result);
}
}
Step 3: The Test Runner
Create src/test/java/com/gyancode/cucumber/runner/TestRunner.java:
package com.gyancode.cucumber.runner;
import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;
@CucumberOptions(
features = "src/test/resources/features",
glue = "com.gyancode.cucumber.stepdefinitions",
plugin = {
"pretty",
"html:target/cucumber-reports/cucumber.html"
},
monochrome = true
)
public class TestRunner extends AbstractTestNGCucumberTests {
}
Step 4: Run It!
# From the project root
mvn test
You should see output like:
Scenario: Add two numbers
Given I have a calculator ✓
When I add 5 and 3 ✓
Then the result should be 8 ✓
Scenario: Subtract two numbers
Given I have a calculator ✓
When I subtract 3 from 10 ✓
Then the result should be 7 ✓
2 Scenarios (2 passed)
6 Steps (6 passed)
Understanding Runner Options
The @CucumberOptions annotation configures everything. Here’s what each option does:
| Option | What It Does | Example |
|---|---|---|
features | Path to feature files | "src/test/resources/features" |
glue | Package(s) containing step definitions | "com.gyancode.cucumber.stepdefinitions" |
plugin | Report formats to generate | "pretty", "html:target/report.html" |
tags | Filter which scenarios to run | "@smoke and not @wip" |
monochrome | Clean console output (no ANSI codes) | true |
dryRun | Check step mappings without running tests | true for validation only |
The dryRun Trick
Set dryRun = true when you write new feature files. Cucumber will tell you which steps are missing without running any tests. It even generates step definition snippets you can copy-paste:
You can implement missing steps with the snippets below:
@Given("I am on the registration page")
public void i_am_on_the_registration_page() {
// Write code here that turns the phrase above into concrete actions
throw new io.cucumber.java.PendingException();
}
Adding Selenium — A Real Example
Now let’s connect Cucumber with Selenium WebDriver for browser automation:
Feature File: login.feature
Feature: User Login
As a registered user
I want to log in to my account
So that I can access my dashboard
Scenario: Successful login
Given I am on the login page
When I enter username "testuser"
And I enter password "password123"
And I click the login button
Then I should see the dashboard
Scenario: Failed login with wrong password
Given I am on the login page
When I enter username "testuser"
And I enter password "wrongpass"
And I click the login button
Then I should see an error message "Invalid credentials"
Step Definitions: LoginSteps.java
package com.gyancode.cucumber.stepdefinitions;
import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import java.time.Duration;
public class LoginSteps {
private WebDriver driver;
private WebDriverWait wait;
@Before
public void setUp() {
driver = new ChromeDriver();
driver.manage().window().maximize();
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
@After
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
@Given("I am on the login page")
public void iAmOnTheLoginPage() {
driver.get("https://example.com/login");
}
@When("I enter username {string}")
public void iEnterUsername(String username) {
WebElement field = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("username"))
);
field.clear();
field.sendKeys(username);
}
@When("I enter password {string}")
public void iEnterPassword(String password) {
WebElement field = driver.findElement(By.id("password"));
field.clear();
field.sendKeys(password);
}
@When("I click the login button")
public void iClickTheLoginButton() {
driver.findElement(By.id("login-btn")).click();
}
@Then("I should see the dashboard")
public void iShouldSeeTheDashboard() {
wait.until(ExpectedConditions.urlContains("/dashboard"));
Assert.assertTrue(driver.getCurrentUrl().contains("/dashboard"));
}
@Then("I should see an error message {string}")
public void iShouldSeeAnErrorMessage(String expectedMessage) {
WebElement error = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.css(".error-message"))
);
Assert.assertEquals(error.getText(), expectedMessage);
}
}
Common Beginner Mistakes
| Mistake | What Happens | Fix |
|---|---|---|
Wrong glue path | Cucumber can’t find step definitions — all steps show as “undefined” | Use the full package name: "com.gyancode.cucumber.stepdefinitions" |
Feature file not in resources | Cucumber can’t find feature files | Place .feature files under src/test/resources/features/ |
| Step text doesn’t match exactly | ”Undefined step” error even though the method exists | The annotation string must match the Gherkin step exactly (including spaces and quotes) |
| Missing Cucumber dependency | Import errors for @Given, @When, @Then | Ensure cucumber-java is in pom.xml |
Runner doesn’t extend AbstractTestNGCucumberTests | Tests don’t execute | The runner class must extend the correct base class |