Cucumber und BDD: warum ich die Feature Datei vor dem Code schreibe
Kurz gesagt
Niveau: Fortgeschritten
Die meiste Nacharbeit beginnt mit einer Anforderung, die zwei Menschen auf zwei Arten lesen. BDD schreibt das Verhalten als konkrete Szenarien auf, die zugleich Tests sind.
- Gherkin: Feature, Rule, Scenario, Given, When, Then, Background, Scenario Outline und Examples. Einfache Sprache mit Struktur.
- Step Definitions machen aus den Worten Aktionen in Capybara. Hilfsmethoden wiederverwenden, keine Schritte, die Schritte aufrufen.
- Outlines, Tags, Hooks, Parametertypen und Page Objects halten eine große Suite lesbar.
- Lebende Dokumentation: Eine Feature Datei, die in jedem Build läuft, bleibt aktuell.
- Einrichtung in Rails: die Integration Cucumber Rails, Capybara, factory_bot und Selenium ab 4.11. Kein Gem webdrivers, kein zweiter Hook für DatabaseCleaner.
Ich nutze Cucumber seit mehr als 10 Jahren bei jedem Projekt, das ich baue, weil es die Vereinbarung darüber, was Software tun soll, ausdrücklich und geprüft macht. Das ist die lange Fassung, warum, mit Gherkin, Step Definitions, den fortgeschrittenen Funktionen, einer Einrichtung in Rails und den häufigen Fehlern, durchgespielt an einem erfundenen Kaffeeshop in Köln und einem SaaS aus Hamburg.
Mein wichtigstes Werkzeug ist kein Editor und keine KI
Das wichtigste Werkzeug in der Art, wie ich Software baue, ist kein schlauer Editor, und es ist auch kein KI Codegenerator. Es ist Cucumber. Ich nutze es seit mehr als 10 Jahren, und zwar bei jedem Projekt, das ich baue: Systeme für die öffentliche Hand, bei denen ein Fehler gefährlich ist und nicht bloß lästig, Logistik für große Unternehmen, SaaS Produkte, die jede Woche ausliefern, Marktplätze und Onlineshops.
Das ist meine Praxis, kein Naturgesetz. Viel gute Software entsteht ohne Cucumber. Aber ich habe keinen besseren Weg gefunden, um sicherzustellen, dass gebaut wird, was das Unternehmen tatsächlich bestellt hat, und dieser Artikel ist die lange Fassung, warum. Er erklärt, was Behaviour Driven Development ist, die Sprache Gherkin, Step Definitions, die fortgeschrittenen Funktionen, die Einrichtung in Rails und die Fehler, die ich am häufigsten sehe.
Was BDD ist
So entsteht noch immer viel Software, ungefähr:
- Jemand aus dem Fachbereich schreibt ein Anforderungsdokument.
- Eine Entwicklerin liest es und versteht es auf eine Art.
- Sie schreibt Code nach diesem Verständnis.
- Ein Tester prüft nach seinem eigenen Verständnis desselben Dokuments.
- Der Fachbereich sieht das Ergebnis und sagt "so war das nicht gemeint".
- Alle streiten, wessen Lesart die richtige war, und die Nacharbeit beginnt.
Niemand in dieser Kette ist nachlässig. Das Dokument ist schlicht mehrdeutig, und drei Menschen lesen es auf drei Arten.
Behaviour Driven Development (BDD) löst das, indem alle das Verhalten gemeinsam aufschreiben, in einer festen Form, die zugleich ein ausführbarer Test ist. Dan North hat es 2006 beschrieben, als Weg, Test Driven Development über Verhalten statt über Tests zu erklären.1 Wie es sich neben Unit Tests einfügt, beschreibe ich in TDD und BDD in Ruby on Rails.
Der Kerngedanke: Wenn die Spezifikation der Test ist, wird die Lücke zwischen dem Gemeinten und dem Gebauten sehr klein. Die Software verhält sich wie spezifiziert, oder der Test wird rot.
Beispiel: ein Satz, zwei Lesarten
Bohnenpost ist ein erfundener Shop für Kaffeeabos in Köln, unter bohnenpost.example. Die Inhaberin schreibt: "Beschädigte Ware bekommt 50 % erstattet." Die Box Roaster's Selection kostet 49,99 €.
Der Entwickler versteht das als 50 % vom Preis, abgerundet, also 24,99 €. Die Inhaberin hat, später gefragt, 25,00 € erwartet, denn "die Hälfte von fünfzig ist fünfundzwanzig". Keiner liegt falsch. Der Satz hat es nicht gesagt. Als Szenario geschrieben muss die Zahl auf dem Papier stehen, bevor irgendwer Code schreibt:
Scenario: Half refund for a damaged bag
Given a delivered order for "Roaster's Selection" at 49,99 €
When customer support approves a 50% refund for damage
Then a refund of 25,00 € should be issued
Die Hälfte von 49,99 € ist €. Das Szenario erzwingt die Entscheidung, kaufmännisch runden, und hält sie dort fest, wo beide sie lesen können.
Was Cucumber macht
Cucumber ist das Werkzeug, das BDD praktikabel macht. Du schreibst Spezifikationen in Gherkin, einer kleinen Sprache, die sich wie einfaches Englisch liest, aber genug Struktur hat, damit ein Computer sie zerlegen und als Tests ausführen kann.2
Hier ist eine Feature Datei:
# features/user_login.feature
Feature: User login
As a registered customer
I want to sign in to my account
So that I can see my subscription
Background:
Given a customer exists with email "sarah@example.com" and password "correcthorse"
Scenario: Successful sign in with valid details
Given I am on the sign in page
When I fill in "Email" with "sarah@example.com"
And I fill in "Password" with "correcthorse"
And I press "Sign in"
Then I should see "Welcome back, Sarah"
And I should be on the "account" page
Scenario: Failed sign in with the wrong password
Given I am on the sign in page
When I fill in "Email" with "sarah@example.com"
And I fill in "Password" with "wrongpassword"
And I press "Sign in"
Then I should see "Invalid email or password"
And I should still be on the "sign in" page
Lies es laut vor. Die Product Ownerin kann folgen, der Geschäftsführer auch, und der Entwickler, der nächstes Jahr dazukommt, ebenso. Genau darum geht es.
Und es ist nicht nur Dokumentation. Wenn ich bundle exec cucumber features/user_login.feature starte, steuert Cucumber die Anwendung über Capybara: Es ruft die Seite auf, füllt die Felder aus, drückt den Knopf und prüft das Ergebnis.3 Verhält sich die Anwendung nicht wie spezifiziert, scheitert der Test.
Die Sprache Gherkin, das Wichtigste
Gherkin hat einen kleinen Wortschatz. Die zentralen Schlüsselwörter sind Feature, Rule, Scenario (oder Example), Given, When, Then, And, But, Background, Scenario Outline und Examples.2
Feature
Jede Datei mit der Endung .feature beginnt mit einem Feature und einer kurzen Beschreibung der Fähigkeit:
Feature: Basket
As an online shopper
I want to manage the items in my basket
So that I can buy several products in one order
Die Zeilen "As a, I want, So that" sind eine Konvention, keine Syntax. Gherkin behandelt sie als freien Text.2 Ich behalte sie trotzdem, weil sie dich zwingen zu sagen, für wen die Funktion ist und warum es sie gibt. Wenn du diese drei Zeilen nicht füllen kannst, verstehst du die Funktion noch nicht gut genug, um sie zu bauen.
Scenario
Ein Szenario ist ein konkretes Beispiel für ein Verhalten:
Scenario: Adding a product to an empty basket
Given my basket is empty
When I add "House Blend 1kg" to my basket
Then my basket should contain 1 item
And the basket total should be 18,50 €
Ein gutes Szenario ist unabhängig (es verlässt sich nicht darauf, dass ein anderes Szenario vorher läuft), fokussiert (ein Verhalten) und klar (jeder erkennt, was geprüft wird).
Die Schlüsselwörter für Schritte
Given legt den Kontext fest, den Zustand der Welt vor der Handlung. When ist die Handlung, das, was die Person tut. Then ist das erwartete Ergebnis.2 And und But setzen die jeweils vorherige Art von Schritt fort, und But liest sich einfach besser für etwas, das nicht passieren soll:
Scenario: Discounted order with free delivery
Given I am signed in as "regular@example.com"
And I have the discount code "SAVE20"
And "Espresso Gift Box" is priced at 39,95 €
When I add "Espresso Gift Box" to my basket
And I apply the discount code
And I go to the checkout
Then I should see the discounted price of 31,96 €
And I should see free delivery applied
But I should not see the express delivery option
Background
Wenn alle Szenarien einer Datei dieselbe Vorbereitung teilen, läuft ein Background vor jedem von ihnen:2
Feature: Product management
Background:
Given I am signed in as an admin
And I am on the product management page
Scenario: Creating a product
When I click "Add product"
And I fill in the product details
And I click "Save"
Then I should see "Product created"
Scenario: Deleting a product
Given a product "Old Blend" exists
When I click "Delete" next to "Old Blend"
And I confirm the deletion
Then "Old Blend" should not appear in the product list
Step Definitions machen aus Worten Handlungen
Feature Dateien sagen, was passieren soll. Step Definitions sagen wie, in Ruby. Die Wörter in Anführungszeichen werden über Cucumber Expressions wie {string} und {int} zu Argumenten.
# features/step_definitions/authentication_steps.rb
Given('I am signed in as {string}') do |email|
@current_user = User.find_by(email: email) || create(:user, email: email)
sign_in_through_the_form(@current_user)
end
Given('I am signed in as an admin') do
@current_user = create(:user, :admin)
sign_in_through_the_form(@current_user)
end
When('I sign out') do
click_button 'Sign out'
end
Then('I should be signed in') do
expect(page).to have_button('Sign out')
end
create kommt aus factory_bot, und Cucumber sieht es erst, wenn World(FactoryBot::Syntax::Methods) in einer Datei unter features/support steht.4 visit, fill_in, click_button und expect(page) kommen aus Capybara.3
Achte darauf, was der Schritt für den Admin nicht tut. Eine ältere Fassung dieses Artikels hat mit step %(...) einen Schritt aus einem anderen aufgerufen. Die eigene Anleitung von Cucumber sagt, dass das in Cucumber Ruby möglich, aber nicht empfohlen ist: Zieh eine Hilfsmethode heraus und ruf sie aus beiden Schritten auf.5 Genau das ist sign_in_through_the_form.
Eine kleine Menge allgemeiner Schritte deckt die meisten Seiten ab:
# features/step_definitions/navigation_steps.rb
PAGES = {
'home' => -> { root_path },
'sign in' => -> { new_user_session_path },
'account' => -> { account_path },
'basket' => -> { cart_path },
'checkout' => -> { checkout_path }
}.freeze
def path_for(name)
route = PAGES.fetch(name.downcase) { raise "No path for '#{name}', add it to PAGES in navigation_steps.rb" }
instance_exec(&route)
end
Given('I am on the {string} page') { |name| visit path_for(name) }
Given('I am on the sign in page') { visit path_for('sign in') }
Then('I should be on the {string} page') { |name| expect(page).to have_current_path(path_for(name)) }
Then('I should still be on the {string} page') { |name| expect(page).to have_current_path(path_for(name)) }
# features/step_definitions/form_steps.rb
When('I fill in {string} with {string}') { |field, value| fill_in field, with: value }
When('I select {string} from {string}') { |value, field| select value, from: field }
When('I check {string}') { |box| check box }
When('I press {string}') { |button| click_button button }
When('I click {string}') { |label| click_on label } # ein Link oder ein Button
# features/step_definitions/assertion_steps.rb
Then('I should see {string}') { |text| expect(page).to have_content(text) }
Then('I should not see {string}') { |text| expect(page).not_to have_content(text) }
Then('I should see {string} within {string}') do |text, selector|
within(selector) { expect(page).to have_content(text) }
end
Eine Liste der Seitennamen, genutzt von den Schritten "on" und "be on", damit die beiden nie auseinanderlaufen. have_current_path wartet und versucht es erneut, ein direkter Vergleich mit current_path tut das nicht, und das zählt, sobald JavaScript im Spiel ist. Und click_on aus Capybara klickt schon einen Link oder einen Button, du musst also nicht eine Ausnahme abfangen und das andere probieren.3
Die fortgeschrittenen Funktionen, die ich nutze
Scenario Outlines für Geschäftsregeln mit vielen Fällen
Muss dasselbe Verhalten mit verschiedenen Eingaben geprüft werden, läuft ein Scenario Outline einmal für jede Zeile seiner Tabelle Examples und füllt die Platzhalter.2
Feature: Subscription pricing
Scenario Outline: Discount codes apply correctly
Given I am on the checkout page for the "<plan>" plan
When I enter the discount code "<code>"
Then the final price should be <final_price>
Examples:
| plan | code | final_price |
| Pro | SAVE10 | 26,99 € |
| Pro | SAVE20 | 23,99 € |
| Pro | FLAT5 | 24,99 € |
| Business | SAVE10 | 89,99 € |
| Business | ENTERPRISE | 69,99 € |
Beispiel: die Preistabelle von Hand prüfen
Schichtwerk ist ein erfundenes SaaS für Schichtplanung in Hamburg, unter schichtwerk.example. Pro kostet 29,99 € im Monat und Business 99,99 €. Bevor die Tabelle in die Feature Datei kommt, prüfen der Inhaber und ich jede Zeile:
- Pro mit SAVE10: , also 26,99 €.
- Pro mit SAVE20: , also 23,99 €.
- Pro mit FLAT5: , also 24,99 €.
- Business mit SAVE10: , also 89,99 €.
- Business mit ENTERPRISE (30 % Rabatt): , also 69,99 €.
Jede Zeile mit Prozentrabatt wird hier abgerundet, aber nur, weil die dritte Nachkommastelle zufällig klein ist. Im Beispiel mit der Erstattung oben wurde aus € kaufmännisch gerundet 25,00 €. Die Tabelle und das Szenario zur Erstattung legen zusammen eine Rundungsregel für das ganze Produkt fest: auf den Cent genau, ab der Hälfte aufrunden.
Tags zum Ordnen und Filtern
Tags fassen Szenarien zu Gruppen zusammen, damit du eine Teilmenge laufen lassen kannst. Cucumber filtert sie mit Tag Ausdrücken, die Tags mit and, or und not verknüpfen.6
@authentication @critical
Feature: Customer authentication
@smoke
Scenario: Successful sign in
Given I am on the sign in page
When I sign in with valid details
Then I should be signed in
@wip
Scenario: Two factor authentication
Given I have two factor authentication turned on
When I enter my password
Then I should be asked for my code
@javascript @external
Scenario: Sign in with Google
Given I am on the sign in page
When I sign in with Google
Then I should be signed in
bundle exec cucumber --tags @smoke
bundle exec cucumber --tags "not @wip"
bundle exec cucumber --tags "@critical and @authentication"
bundle exec cucumber --tags "@javascript and not @external"
Die Tags, die ich typischerweise nutze: @smoke für schnelle Prüfungen bei jedem Commit, @regression für die ganze Suite vor einem Release, @critical für alles, was vor jedem Deployment bestehen muss, @slow für lange Szenarien, @wip für Arbeit in Arbeit, die aus der Continuous Integration herausgehalten wird, und @external für alles, was einen externen Dienst braucht. @javascript ist der einzige Tag mit eingebauter Bedeutung: Capybara schaltet dieses Szenario auf seinen Treiber mit JavaScript um, der ohne Weiteres headless laufen kann.3
Hooks für Vorbereitung und Aufräumen
Hooks führen Code vor oder nach Szenarien aus, auf Wunsch nur für Szenarien, die zu einem Tag Ausdruck passen.6
# features/support/hooks.rb
Before do
ActionMailer::Base.deliveries.clear
Timecop.freeze(Time.zone.parse('2026-04-08 10:00'))
end
After do |scenario|
Timecop.return
if scenario.failed? && Capybara.current_driver != :rack_test
page.save_screenshot("tmp/screenshots/#{scenario.name.parameterize}.png")
end
end
Before('@external') do
next if ENV['USE_REAL_APIS'] == 'true'
stub_request(:post, %r{api\.stripe\.com}).to_return(status: 200, body: { id: 'pi_test' }.to_json)
end
Der Screenshot läuft nur, wenn ein echter Browser steuert, weil der Standardtreiber rack_test keinen Bildschirm hat, den er festhalten könnte. Zwei Hooks aus der älteren Fassung habe ich entfernt: einen, der für @javascript den Treiber umschaltete, was Capybara schon selbst tut, und einen, der Datumsangaben aus Kommentaren im Szenario las, was cleverer als klar war.
Eigene Parametertypen für deine Fachsprache
Cucumber lässt sich die Wörter deiner Domäne beibringen, damit ein Schritt {money} sagen kann statt eines regulären Ausdrucks:
# features/support/parameter_types.rb
ParameterType(
name: 'money',
regexp: /\d[\d.]*(?:,\d{2})? €/, # deutsches Format: 1.299,99 €
transformer: ->(amount) { BigDecimal(amount.delete('. €').tr(',', '.')) }
)
ParameterType(
name: 'order_state',
regexp: /cart|address|delivery|payment|confirm|complete|canceled/,
transformer: ->(state) { state }
)
Scenario: A large discount
Given the product costs 1.299,99 €
When I apply a discount of 200,00 €
Then the final price should be 1.099,99 €
Geld wird zu einem BigDecimal, nie zu einer Gleitkommazahl, weil Floats die meisten Centbeträge nicht exakt halten können. Vergleich es mit einem BigDecimal, das von der Seite gelesen wurde, nicht mit to_f.
Page Objects, damit eine Änderung eine Datei betrifft
Bei komplexen Seiten verpacke ich die Selektoren in ein Page Object. Ändert sich das Design, ändert sich eine Datei, und die Step Definitions bleiben, wie sie sind.
# features/support/pages/checkout_page.rb
class CheckoutPage
include Capybara::DSL
def fill_address(address)
within('#shipping-address') do
fill_in 'Address line 1', with: address[:line1]
fill_in 'Town or city', with: address[:city]
fill_in 'Postcode', with: address[:postcode]
end
end
def apply_discount_code(code)
fill_in 'Discount code', with: code
click_button 'Apply'
end
def place_order = click_button('Place order')
def order_total
BigDecimal(find('#order-total').text.delete('. €').tr(',', '.')) # deutsches Format: 1.099,99 €
end
end
# features/step_definitions/checkout_steps.rb
def checkout_page = (@checkout_page ||= CheckoutPage.new)
When('I fill in my delivery address') do
checkout_page.fill_address(line1: 'Ehrenstraße 12', city: 'Köln', postcode: '50672')
end
When('I apply the discount code {string}') { |code| checkout_page.apply_discount_code(code) }
Then('the order total should be {money}') do |expected|
expect(checkout_page.order_total).to eq(expected)
end
Hilfsmethoden in der World
Cucumber Ruby führt jedes Szenario in einem frischen Objekt aus, das es World nennt, und jeder Schritt läuft in diesem Kontext, du kannst also eigene Module hineinmischen.7 Dort leben Hilfsmethoden wie sign_in_through_the_form:
# features/support/world_extensions.rb
module AuthenticationHelpers
def sign_in_through_the_form(user, password: 'password123')
visit new_user_session_path
fill_in 'Email', with: user.email
fill_in 'Password', with: password
click_button 'Sign in'
expect(page).to have_button('Sign out')
end
end
module MailHelpers
def emails_sent_to(address) = ActionMailer::Base.deliveries.select { |m| m.to.include?(address) }
end
World(AuthenticationHelpers, MailHelpers)
Feature Dateien als lebende Dokumentation
Das ist der am meisten unterschätzte Nutzen. Wie oft ist die Dokumentation eines Projekts veraltet? Das Anforderungsdokument vom letzten Frühjahr, die Wikiseite, die beschreibt, wie es früher lief, die README, die schon am Tag ihres Entstehens optimistisch war.
Eine Feature Datei, die in jedem Build läuft, kann sich nicht weit von der Wahrheit entfernen, ohne dass es jemand merkt, denn eine grüne Suite heißt, dass das beschriebene Verhalten da ist. Sie dokumentiert nur, was sie abdeckt, sie ist also nicht die ganze Wahrheit, aber was sie sagt, ist aktuell. Die wichtigen baue ich so auf, dass man sie lesen kann:
# features/orders/order_lifecycle.feature
@orders
Feature: Order lifecycle
Orders move from the basket to completion, and some come back.
This file documents and checks the valid transitions.
Background:
Given a customer with email "customer@example.com"
And a product "House Blend 1kg" priced at 18,50 €
Rule: A paid order is confirmed and reduces stock
Scenario: Normal order
Given the customer has "House Blend 1kg" in the basket
When they complete the checkout and the payment succeeds
Then the order should be "complete"
And the stock of "House Blend 1kg" should fall by 1
And the customer should receive an order confirmation email
Scenario: Card declined
Given the customer has "House Blend 1kg" in the basket
When they complete the checkout and the card is declined
Then the order should still be at "payment"
And the stock should not change
Rule: Orders can be cancelled until they ship
Scenario: Cancelled before dispatch
Given the customer has a complete order that has not shipped
When they cancel it
Then a full refund of 18,50 € should be issued
And the stock should be restored
Scenario: Too late to cancel
Given the customer has an order that has shipped
When they try to cancel it
Then the cancellation should be refused
And they should be pointed to the returns page
Rule, verfügbar seit Gherkin 6, fasst die Szenarien zusammen, die eine Geschäftsregel veranschaulichen.2 Eine neue Entwicklerin kann diese Datei lesen und weiß, wie Bestellungen funktionieren, ohne sich durch den Code zu graben.
Cucumber in einem Rails Projekt einrichten
# Gemfile
group :test do
gem 'cucumber-rails', require: false
gem 'database_cleaner-active_record'
gem 'capybara'
gem 'selenium-webdriver'
gem 'factory_bot_rails'
gem 'timecop'
gem 'webmock'
end
Die ältere Fassung dieser Liste enthielt das Gem webdrivers. Seine eigene README sagt inzwischen, dass du es ab Selenium 4.11 nicht mehr laden sollst, weil Selenium Manager den Browsertreiber für dich findet und installiert.89
bundle install
bin/rails generate cucumber:install
Der Generator legt features/support/env.rb an. Ich ergänze nur, was die Voreinstellungen nicht abdecken:310
# features/support/env.rb (nach den generierten Zeilen)
Capybara.default_driver = :rack_test # schnell, ohne JavaScript
Capybara.javascript_driver = :selenium_chrome_headless # von Capybara schon registriert
Capybara.default_max_wait_time = 5
Cucumber::Rails::Database.javascript_strategy = :truncation
World(FactoryBot::Syntax::Methods)
require 'webmock/cucumber'
WebMock.disable_net_connect!(allow_localhost: true)
Es gibt keine eigene Datei, die DatabaseCleaner.start und DatabaseCleaner.clean aufruft. Standardmäßig führt cucumber-rails beides selbst um jedes Szenario herum aus, es noch einmal zu tun, macht die Suite nur langsamer.10
bundle exec cucumber # alles
bundle exec cucumber features/orders/order_lifecycle.feature:14 # ein Szenario, nach Zeile
bundle exec cucumber --format html --out reports/cucumber.html
Die Fehler, die ich am häufigsten sehe
Die Umsetzung testen statt des Verhaltens
# Nicht so
Scenario: Create user record
When I POST to /api/users with JSON {"name": "Dave"}
Then the response status should be 201
And the users table should have 1 row
# Sondern so
Scenario: Registering a new account
Given I am on the registration page
When I register with the name "Dave"
Then I should see "Welcome, Dave"
And I should receive a welcome email
Das erste bricht, sobald sich die API ändert, auch wenn das Verhalten gleich bleibt. Das zweite prüft, was der Kundin wichtig ist. API Verträge verdienen auch Tests, auf ihrer eigenen Ebene.
Schritte, die am Markup hängen
When I click on the button with id "submit-form" and class "btn-primary" bricht bei jedem Redesign. When I submit the form überlebt es. Selektoren gehören in Step Definitions und Page Objects, nie in die Feature Datei.
Eine Datei mit 50 Szenarien
Teil nach Fähigkeit auf: features/orders/placing_orders.feature, features/orders/cancellation.feature, features/users/registration.feature und so weiter.
Riesige Schritte, oder Schritte, die Schritte aufrufen
Ein Schritt, der 100 Zeilen Code versteckt, oder einer, der fünf andere Schritte aufruft, ist schwer zu debuggen. Setz sie aus Hilfsmethoden zusammen, wie bei sign_in_through_the_form.5
Beispiel: ein schlechtes Szenario umschreiben
Der erste Entwurf von Bohnenpost für Geschenkabos lautete:
Scenario: Gift
Given I click "#gift-toggle"
And I fill in "gift_email" with "friend@example.com"
When I press "Pay"
Then the gift_subscriptions table should have 1 row
Drei Probleme: ein Given, das eine Handlung ist, Selektoren und Feld Ids im Text, und eine Prüfung der Datenbank statt eines Ergebnisses, das ein Mensch sieht. Umgeschrieben:
Scenario: Sending a three month gift subscription
Given I am buying the "3 month gift" subscription
When I send it to "friend@example.com" and pay
Then I should see "Your gift is on its way"
And "friend@example.com" should receive a gift email
Die Inhaberin kann es jetzt lesen, freigeben und das bemerken, wonach niemand gefragt hatte: was in der Geschenkmail stehen soll.
Warum es für mich zur Arbeit gehört
Ich baue kein Projekt ohne Cucumber oder eine gleichwertige BDD Schicht. Das ist meine Regel, und das bringt sie:
- Klare Anforderungen. Das Verhalten steht genau fest, bevor der Code entsteht, also verschwindet "ich dachte, du meinst" weitgehend.
- Weniger Nacharbeit. Wenn das Szenario die Spezifikation ist, schrumpft die Lücke zwischen Bestelltem und Gebautem.
- Refactoring ohne Angst. Sind die wichtigen Wege abgedeckt, kann ich Code umbauen und weiß innerhalb von Minuten, ob etwas kaputt ist.
- Dokumentation, die aktuell bleibt. Neue Leute lesen die Feature Dateien.
- Eine Sprache für alle. Inhaber, Designerinnen und Entwickler lesen und schreiben dieselben Szenarien.
- Fehler, die behoben bleiben. Jede Fehlerbehebung kommt mit einem Szenario, das den Fehler gefunden hätte.
Nichts davon macht Software perfekt. Es macht die Vereinbarung darüber, was die Software tun soll, ausdrücklich, geprüft und sichtbar, und genau dort beginnt der meiste Ärger, den ich sehe.
Deine erste Feature Datei
- Such dir einen Weg aus, der zählt, etwa die Anmeldung oder den Checkout.
- Schreib die Feature Datei zuerst in einfacher Sprache. Denk noch nicht an Step Definitions.
- Zeig sie jemandem, der keinen Code schreibt, und frag, ob sie sagt, was gemeint ist.
- Schreib die Step Definitions, eine nach der anderen.
- Lass sie laufen und sieh zu, wie sie besteht, dann mach den Code absichtlich kaputt und sieh zu, wie sie scheitert.
- Nimm den nächsten Weg dazu.
Innerhalb weniger Wochen merkst du, dass sich die Gespräche ändern, bevor sich der Code ändert.
Footnotes
-
Dan North, Introducing BDD, zuerst erschienen im Magazin Better Software, März 2006, https://dannorth.net/blog/introducing-bdd/
-
Cucumber, Gherkin reference, https://cucumber.io/docs/gherkin/reference/
-
Capybara, README, https://github.com/teamcapybara/capybara
-
thoughtbot, factory_bot Getting Started (Konfiguration für RSpec und Cucumber), https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md
-
Cucumber, Cucumber anti patterns, conjunction steps, https://cucumber.io/docs/guides/anti-patterns/
-
Cucumber, API reference: tag expressions and hooks, https://cucumber.io/docs/cucumber/api/
-
Cucumber, Sharing state between steps, https://cucumber.io/docs/cucumber/state/
-
Titus Fortner, webdrivers README, update on the future of this project, https://github.com/titusfortner/webdrivers
-
Selenium, Selenium Manager, Selenium Dokumentation, https://www.selenium.dev/documentation/selenium_manager/
-
Cucumber, cucumber-rails README, https://github.com/cucumber/cucumber-rails