Advanced Chapter 20 · 12 min read

Best Practices & Anti-Patterns

Write maintainable BDD suites — avoid common anti-patterns, follow Gherkin writing guidelines, and structure scalable Cucumber projects.

BDD Best Practices & Anti-Patterns

The difference between a BDD suite that scales and one that becomes a maintenance nightmare comes down to how you write scenarios, structure step definitions, and organize your project.

Golden Rules

  • Write declarative, not imperative — describe what, not how
  • One scenario = one behavior — don't test everything in one scenario
  • Thin steps, fat page objects — keep step definitions as thin orchestrators
  • Use Background wisely — only for truly common preconditions
  • Tags over folders — organize by tags, not deep folder hierarchies

Common Anti-Patterns

  • UI-coupled stepsWhen I click the #submit-btn (too technical)
  • Scenario novels — 20+ steps in one scenario (too long)
  • Incidental details — specifying data that doesn't affect the outcome
  • Coupled scenarios — Scenario B depends on Scenario A running first
good_vs_bad.feature
# ===== BAD: Imperative (how) =====
# Scenario: Login
#   Given I open Chrome browser
#   And I navigate to "https://app.com/login"
#   And I find element by id "username"
#   And I type "admin" into it
#   And I find element by id "password"
#   And I type "secret" into it
#   And I click the element with css ".btn-submit"
#   And I wait 3 seconds
#   Then the URL should be "https://app.com/dashboard"

# ===== GOOD: Declarative (what) =====
Feature: User Authentication

  Scenario: Admin can access the dashboard
    Given the user is on the login page
    When the user logs in as "admin"
    Then the dashboard should be displayed

  # GOOD: Scenario Outline for data variations
  Scenario Outline: Role-based access
    Given the user logs in as "<role>"
    Then they should see the "<section>" section

    Examples:
      | role    | section       |
      | admin   | User Mgmt     |
      | manager | Team Reports  |
      | viewer  | Read-Only     |

  # GOOD: Independent scenarios (no coupling)
  Scenario: New user sees onboarding
    Given a newly registered user
    When they log in for the first time
    Then the onboarding wizard should appear

  # GOOD: Business-readable, no technical details
  Scenario: Expired session requires re-login
    Given the user is logged in
    And the session has expired
    When the user tries to access the dashboard
    Then they should be redirected to the login page
architecture-tips.txt
# Project Architecture Best Practices

1. STEP DEFINITIONS: Keep thin, delegate to helpers
   LoginSteps -> LoginPage -> WebDriver
   NOT: LoginSteps -> WebDriver directly

2. FEATURE FILES: One feature per business capability
   login.feature, checkout.feature, search.feature
   NOT: test1.feature, test2.feature

3. TAGS: Use consistently
   @smoke     — critical happy paths (run on every commit)
   @regression — full suite (run nightly)
   @wip       — work in progress (always excluded)
   @api       — no browser needed
   @ui        — browser required

4. SCENARIOS: 3-7 steps each
   Too short = not testing enough
   Too long = testing too much at once

5. STEP REUSE: >80% of steps should be reusable
   Generic: "the user clicks the {string} button"
   NOT: "the user clicks the login page submit button"

6. DATA: Use Scenario Outline for data variations
   NOT: copy-paste scenarios with different values

7. INDEPENDENCE: Each scenario runs in isolation
   Setup via Background or hooks, not by running other scenarios
good_vs_bad.feature
# ===== BAD: Imperative (how) =====
# Scenario: Login
#   Given I open Chrome browser
#   And I navigate to "https://app.com/login"
#   And I find element by id "username"
#   And I type "admin" into it
#   Then the URL should be "https://app.com/dashboard"

# ===== GOOD: Declarative (what) =====
Feature: User Authentication

  Scenario: Admin can access the dashboard
    Given the user is on the login page
    When the user logs in as "admin"
    Then the dashboard should be displayed

  Scenario Outline: Role-based access
    Given the user logs in as "<role>"
    Then they should see the "<section>" section

    Examples:
      | role    | section       |
      | admin   | User Mgmt     |
      | manager | Team Reports  |
      | viewer  | Read-Only     |

  Scenario: New user sees onboarding
    Given a newly registered user
    When they log in for the first time
    Then the onboarding wizard should appear

  Scenario: Expired session requires re-login
    Given the user is logged in
    And the session has expired
    When the user tries to access the dashboard
    Then they should be redirected to the login page
behave-tips.txt
# Behave-Specific Best Practices

1. CONTEXT OBJECT: Use wisely
   context.driver = webdriver       # OK — shared state
   context.test_data = {}           # OK — scenario data
   context.some_flag = True         # Avoid — use tags instead

2. ENVIRONMENT.PY: Single source for hooks
   before_scenario: setup driver
   after_scenario: screenshot + quit
   before_tag: conditional setup

3. STEP FILE ORGANIZATION:
   common_steps.py    — generic, reusable steps
   login_steps.py     — feature-specific steps
   api_steps.py       — API testing steps
   NOT: steps.py      — one giant file

4. TAGS: Filter effectively
   behave --tags="@smoke and not @wip"
   behave --tags="@api"  (no browser needed)

5. REPORTING: Always generate reports
   behave -f allure_behave.formatter:AllureFormatter -o reports/
   behave --junit --junit-directory=reports/junit/

6. HEADLESS IN CI:
   if os.getenv('HEADLESS'):
       options.add_argument('--headless=new')

Cucumber BDD Advanced Best Practices & Anti-Patterns

Written by PV

© 2026 All Rights Reserved