Przejdź do głównej zawartości

Behavior-driven development (BDD) z agentami AI traktuje scenariusze Gherkin, zapisane jako Given/When/Then i zatwierdzone przez człowieka, jako wykonywalny kontrakt, pod który agent implementuje kod. Agent szkicuje scenariusze i kod kroków Cucumbera, człowiek zatwierdza jedno i drugie, a sesja implementująca nie może ich zmienić. O tym, kiedy zachowanie jest gotowe, rozstrzyga przebieg Cucumbera w CI.

Ta strona jest dla programistów i tech leadów, u których product owner albo testerzy już czytają scenariusze, albo którzy utrzymują zestaw testów Cucumbera. Twój koszyk przechodzi wszystkie testy jednostkowe, a i tak trafia na produkcję z błędem: reguła stanów magazynowych żyła w tickecie, którego agent nigdy nie zobaczył, a gdy scenariusz w końcu się wysypał, agent zamienił Then the cart holds 2 na Then the cart holds at least 1. BDD rozwiązuje pierwszy problem tylko wtedy, gdy rozwiążesz też drugi.

  • Regułę decyzyjną: kiedy Gherkin zarabia na swoją dodatkową warstwę, a kiedy wystarczą zwykłe testy akceptacyjne.
  • Pętlę w pięciu krokach: example mapping, zablokowany plik .feature, kod kroków czerwony z właściwego powodu, implementacja i porządki w bibliotece kroków.
  • Cztery prompty do skopiowania dla Claude Code, Codeksa i Cursora oraz blokadę sesji dla każdego narzędzia.
  • Job CI, który uruchamia testy, odrzuca zmiany kontraktu i nieużywane kroki oraz publikuje raport HTML jako dowód, który recenzent czyta zamiast diffu.
  • Sposoby, w jakie BDD z agentami zawodzi, łącznie z pułapką kodu wyjścia Cucumber.js, i drogę wyjścia z każdego z nich.

Kiedy Gherkin się opłaca zamiast zwykłych testów akceptacyjnych?

Dział zatytułowany „Kiedy Gherkin się opłaca zamiast zwykłych testów akceptacyjnych?”

Gherkin dokłada warstwę: plik .feature oraz definicje kroków, które wiążą każde zdanie z kodem. Ta warstwa się zwraca, gdy zachowanie zatwierdzają osoby, które nie czytają kodu. W przeciwnym razie to jeszcze jedno miejsce, w którym agent schowa słabą asercję. Ogólna metoda, czyli jedno kryterium to jeden czerwony test, jest opisana w artykule o wykonywalnych kryteriach akceptacji; ta strona dotyczy wariantu z Gherkinem.

SytuacjaUżyjDlaczego
Scenariusze zatwierdza product owner, tester albo ekspert domenowyGherkin + CucumberPodpisują zdania, a nie kod testów
Jedna reguła ma wiele przykładów (ceny, limity, lokalizacje)Scenario Outline w GherkinieTabela Examples sprawia, że wpisywanie przypadków na sztywno widać od razu, a nowy wiersz kosztuje niewiele
Utrzymujesz już testy w Cucumberze, Behacie albo SpecFlowGherkinAgent rozbudowuje istniejącą bibliotekę kroków zamiast zaczynać drugi styl
Testy czytają tylko programiściZwykłe testy Playwright albo APIWarstwa kroków kosztuje więcej, niż daje
Zachowanie nie ma granicy widocznej dla użytkownikaTesty jednostkowe albo testy właściwościScenariusz przywiązałby się do wnętrza kodu

W projektach JavaScript wybierz jeden z dwóch runnerów. Cucumber.js (@cucumber/cucumber 13.2.1 w npm, sprawdzone 2026-09-26) uruchamia pliki .feature bezpośrednio i steruje Playwrightem jako biblioteką. playwright-bdd (9.2.1, peer @playwright/test 1.44 lub nowszy) zamienia pliki .feature w pliki Playwright Test poleceniem npx bddgen, więc zachowujesz fixtures, trace’y i sharding Playwrighta. Przykład poniżej używa Cucumber.js; pętla i prompty są takie same w obu przypadkach.

Pętla składa się z dwóch pull requestów. Pierwszy niesie kontrakt: plik .feature i czerwony kod kroków. Zatwierdza go człowiek. Drugi niesie implementację z sesji, która nie może dotknąć pierwszego. Pull request z kontraktem jest czerwony z założenia, więc nigdy nie jest scalany samodzielnie: otwórz pull request z implementacją z gałęzią kontraktu jako bazą, scal go do tej gałęzi, gdy CI będzie zielone, a potem scal do main pull request z kontraktem, który jest już zielony. W main nigdy nie ląduje czerwony scenariusz, a strażnik ścieżek nadal porównuje implementację z zatwierdzonym kontraktem.

  1. Zmapuj przykłady z agentem. Agent zamienia historyjkę w reguły, konkretne przykłady i otwarte pytania. Ty i product owner odpowiadacie na pytania i zatwierdzacie scenariusze.
  2. Dodaj plik .feature do pull requesta z kontraktem. Od tej chwili scenariusze (features/**/*.feature) należą do product ownera, a kod kroków (features/steps/, features/support/) do tech leada.
  3. Wygeneruj kod kroków, który pada z właściwego powodu. Osobna sesja pisze definicje kroków i uruchamia zestaw testów. Każdy scenariusz musi padać na brakującym zachowaniu, a nie na brakującym selektorze czy rzuconym TypeError.
  4. Implementuj do zielonego w świeżej sesji, która ładuje blokadę, we własnym worktree, na gałęzi opartej na gałęzi kontraktu.
  5. Skonsoliduj bibliotekę kroków po wydaniu funkcji, żeby kolejna historyjka używała istniejących kroków zamiast dopisywać niemal identyczne.

Example mapping dzieli historyjkę na reguły, jeden lub więcej przykładów na regułę oraz pytania, na które nikt jeszcze nie zna odpowiedzi. Pierwsze przejście zleć agentowi. Czyta ticket i kod szybciej, niż zbierze się spotkanie, a jego pytania odsłaniają luki.

Szkic to propozycja. Product owner odpowiada na pytania, usuwa tagi @question i zatwierdza plik. Oto zatwierdzony wynik dla koszyka ograniczonego stanem magazynowym:

features/cart/stock-limit.feature
Feature: Stock-limited cart
Shoppers cannot put more units in the cart than the warehouse holds.
Rule: The cart never holds more units than are in stock
Scenario Outline: Adding up to the stock limit
Given "<product>" has <stock> units in stock
When the shopper adds <qty> "<product>" to the cart
Then the cart holds <qty> "<product>"
Examples:
| product | stock | qty |
| Super Widget | 2 | 2 |
| Gear Kit | 5 | 1 |
Scenario: Blocked from exceeding the stock limit
Given "Super Widget" has 2 units in stock
And the shopper already has 2 "Super Widget" in the cart
When the shopper tries to add 1 more "Super Widget"
Then the shopper sees "Only 2 in stock"
And the cart holds 2 "Super Widget"

Drugi wiersz przykładów ma znaczenie. Przy jednym przykładzie agent może przejść test, sprawdzając "Super Widget" i 2. Przy dwóch musi zaimplementować regułę. Zwróć uwagę na brzmienie kroku przygotowującego w drugim scenariuszu. Cucumber dopasowuje krok do definicji bez względu na słowo kluczowe Given/When/Then, więc krok przygotowujący stan i asercja potrzebują różnych zdań: the shopper already has 2 … in the cart ustawia stan, a the cart holds 2 … zawsze tylko sprawdza. Gdyby oba używały tego samego zdania, warunek wstępny albo sprawdzałby pusty koszyk i padał, albo Then stałby się krokiem przygotowującym, który niczego nie sprawdza. Jeśli product owner pisze po polsku, Gherkin to obsługuje: # language: pl w pierwszej linii przełącza słowa kluczowe na Funkcja, Scenariusz, Zakładając, Gdy, Wtedy (pełną listę pokazuje npx cucumber-js --i18n-keywords pl).

Dodaj plik .feature do pull requesta z kontraktem, który może zaakceptować tylko właściciel kodu (code owner), oznacz go etykietą bdd-contract i dodaj strażnika CI pokazanego w sekcji Jak udowodnić zachowanie bez czytania diffu?. Product owner akceptuje kontrakt w tym pull requeście, ale pull request pozostaje otwarty: testy są czerwone, dopóki nie trafi do niego implementacja. Pull request z implementacją celuje w gałąź kontraktu, a nie w main, i oba są scalane, gdy testy przejdą. Pięć warstw ochrony, łącznie z przykładami holdout trzymanymi poza repozytorium, opisuje artykuł o ochronie wyroczni.

Krok 3: wygeneruj kod kroków czerwony z właściwego powodu

Dział zatytułowany „Krok 3: wygeneruj kod kroków czerwony z właściwego powodu”

Skonfiguruj Cucumber.js raz. Ten cucumber.json ładuje pliki kroków w TypeScripcie i zapisuje raport HTML:

{
"default": {
"paths": ["features/**/*.feature"],
"import": ["features/steps/**/*.ts", "features/support/**/*.ts"],
"format": ["progress", "html:reports/cucumber.html"]
}
}
{
"scripts": {
"test:bdd": "NODE_OPTIONS='--import tsx' cucumber-js"
}
}

Ładuj tsx przez NODE_OPTIONS, a nie przez opcję loader Cucumbera: z tsx 4.23 na Node 22 wpis "loader": ["tsx"] kończy się błędem „tsx must be loaded with —import instead of —loader” (sprawdzone 2026-09-26 z @cucumber/cucumber 13.2.1). Zapis z NODE_OPTIONS to składnia powłoki POSIX; na Windowsie użyj cross-env.

Uruchom npm run test:bdd -- --dry-run, żeby zobaczyć szkielety kodu, które Cucumber proponuje dla niezdefiniowanych kroków, a potem przekaż zadanie sesji, która może pisać wyłącznie w features/steps/ i features/support/. W Claude Code zablokuj scenariusze, ale nie kod kroków:

Okno terminala
claude --disallowedTools "Edit(/features/**/*.feature)"

Codex 0.157.1 i Cursor nie mają zweryfikowanej blokady zapisu dla ścieżki, więc na tym etapie jedyną kontrolą nienaruszalności scenariuszy jest recenzja pull requesta z kontraktem przez właściciela kodu.

Najpierw uruchom aplikację (npm start w drugim terminalu): test:bdd steruje aplikacją na porcie 3000, a bez niej każdy scenariusz pada z odmową połączenia, czyli z niewłaściwego powodu.

Czytaj czerwony przebieg, a nie kod kroków linijka po linijce. Każda porażka powinna nazywać brakujące zachowanie, na przykład oczekiwany tekst Only 2 in stock, który się nie pojawia. Dodaj kod kroków w commicie razem z plikiem .feature w tym samym pull requeście z kontraktem.

Sesja implementująca dostaje kontrakt i nic innego do zmiany. Daj jej gałąź opartą na gałęzi kontraktu (tu cart-stock-contract) we własnym worktree, żeby plik .feature i kod kroków były już w kopii roboczej. Wbudowana flaga worktree narzędzia (claude --worktree, codex exec --worktree) startuje z domyślnej bazy narzędzia, a nie z gałęzi kontraktu, więc utwórz worktree samodzielnie:

Okno terminala
git worktree add ../cart-stock-impl -b cart-stock-impl cart-stock-contract
cd ../cart-stock-impl
ls features/cart/stock-limit.feature # the contract must be here

Utrzymuj aplikację uruchomioną na porcie 3000 z tego worktree (npm start), tak jak w kroku 3. Sposób blokady sesji zależy od narzędzia.

Przekaż blokadę jako flagę wiersza poleceń tylko sesjom implementującym, żeby nie blokowała sesji piszącej kod kroków w kroku 3:

Okno terminala
# Terminal, inside ../cart-stock-impl: interactive implementation
claude --disallowedTools "Edit(/features/**)"
# Terminal or CI: headless run with a spending cap
claude -p "Implement features/cart/stock-limit.feature until npm run test:bdd passes. Do not edit features/." \
--disallowedTools "Edit(/features/**)" \
--permission-mode acceptEdits \
--allowedTools "Bash(npm run test:bdd)" "Bash(npm run test:bdd *)" \
--max-budget-usd 5

Reguły Edit obejmują własne narzędzia plikowe Claude Code i polecenia plikowe, które rozpoznaje w Bashu, ale nie skrypt Node zapisujący pliki, więc wiążącą kontrolą pozostaje strażnik CI. Przed pierwszym prawdziwym przebiegiem poproś sesję interaktywną, żeby dopisała linię do features/cart/stock-limit.feature: edycja musi zostać odrzucona. Flagi sprawdzone na Claude Code 2.1.283 w dniu 2026-09-26; składnię reguł wyjaśnia artykuł o uprawnieniach i sandboksach.

Jeśli sesja pisząca kod kroków ma najpierw obejrzeć działającą stronę, zanim wybierze lokatory, podłącz serwer Playwright MCP (@playwright/mcp). Konfigurację dla wszystkich trzech narzędzi opisuje artykuł o serwerach MCP do automatyzacji przeglądarki.

Agenci piszą nowy krok dla każdego sformułowania: „adds 2 to the cart”, „puts 2 in the cart”, „adds two items”. Formatter usage Cucumbera wypisuje każdą definicję kroku razem ze scenariuszami, które jej używają, a nieużywane definicje oznacza jako UNUSED.

Recenzent czyta trzy rzeczy: zatwierdzony plik .feature, raport HTML Cucumbera i wynik strażnika. Ten job GitHub Actions dostarcza wszystkie trzy przy każdym pull requeście:

.github/workflows/bdd.yml
name: bdd
on: pull_request
permissions:
contents: read
jobs:
bdd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npm ci
- run: npx playwright install --with-deps chromium
- name: Implementation PRs must not change the contract
if: ${{ !contains(github.event.pull_request.labels.*.name, 'bdd-contract') }}
env:
BASE_REF: ${{ github.base_ref }}
run: |
changed=$(git diff --name-only "origin/$BASE_REF"...HEAD -- features)
if [ -n "$changed" ]; then
echo "::error::This PR changes the BDD contract:"; echo "$changed"; exit 1
fi
- name: No unused step definitions
run: |
if npm run test:bdd -- --dry-run --format usage | grep -q UNUSED; then
echo "::error::Unused step definitions. Consolidate them."; exit 1
fi
- run: npm run build
- run: npx start-server-and-test start http://localhost:3000 test:bdd
- uses: actions/upload-artifact@v7
if: always()
with:
name: cucumber-report
path: reports/

Job zakłada skrypt start, który serwuje zbudowaną aplikację na porcie 3000; dodaj start-server-and-test do devDependencies, żeby npx nie pobierał go przy każdym uruchomieniu.

W pull requeście z kontraktem ostatni krok npm run test:bdd pada z założenia; ten czerwony przebieg jest dowodem, że kod kroków pada z właściwego powodu. Pull requesta nie scala się, dopóki jest czerwony: najpierw do gałęzi kontraktu trafia oparty na niej pull request z implementacją, job przechodzi ponownie na zielono i dopiero wtedy kontrakt trafia do main. Ustaw job bdd jako wymagany status check dla main, żeby czerwonego kontraktu nie dało się scalić przez pomyłkę. Chroń także gałąź kontraktu, żeby kod trafiał do niej wyłącznie przez pilnowany przez strażnika pull request z implementacją; jeśli nieaktualne akceptacje są odrzucane (Dismiss stale approvals), product owner ponownie akceptuje zielony już pull request z kontraktem przed scaleniem.

Rozdziel własność w .github/CODEOWNERS wpisami /features/**/*.feature @your-org/product-owners i /features/steps/ @your-org/tech-leads oraz /features/support/ @your-org/tech-leads i /.github/ @your-org/tech-leads (jeden wzorzec na linię), a potem włącz Require review from Code Owners. Pull request z etykietą bdd-contract wymaga wtedy akceptacji product ownera dla brzmienia scenariuszy i tech leada dla kodu kroków, więc osłabionej asercji nie zatwierdza nikt, kto nie czyta kodu. Job nie ma dostępu do sekretów i używa tokenu tylko do odczytu, więc można go bezpiecznie uruchamiać na pull requestach z gałęzi. Sam w sobie nie jest jednak odporny na obejście: przy wyzwalaczu pull_request GitHub uruchamia bdd.yml z gałęzi pull requesta, więc pull request z implementacją może usunąć strażnika ścieżek i przejść, a każdy, kto może dodać etykietę bdd-contract, może sprawić, że strażnik zostanie pominięty. Dlatego w CODEOWNERS jest też /.github/: przy wymaganym przeglądzie Code Owners ani zmieniony workflow, ani zmiana w features/ nie zostanie scalona bez właściciela.

Co jest sprawdzaneJakKto zatwierdza
Scenariusze opisują właściwe zachowaniePull request z kontraktem i plikiem .featureProduct owner
Kod kroków pada z właściwego powoduCzerwony przebieg wklejony do pull requesta z kontraktemProgramista albo tech lead
Kod kroków jest wystarczająco mocnyPrompt audytowy z artykułu o wykonywalnych kryteriach akceptacji, wynik mutacyjny z artykułu o sile wyroczniTech lead, raz na zestaw testów
Implementacja spełnia kontraktnpm run test:bdd zielony w CI, raport HTML w artefaktachCI
Kontrakt nie przesunął się pod kodStrażnik ścieżek i CODEOWNERSCI, a do tego product owner dla scenariuszy i tech lead dla kodu kroków

Bezpieczeństwo, płatności i migracje nadal przechodzą ludzki code review. Scenariusze zmieniają domyślny tryb dla całej reszty; tę zmianę opisuje artykuł o czytaniu dowodów zamiast kodu.

Agent edytuje scenariusz albo zmienia jego tagi. Dopisuje @wip lub @skip do czerwonego scenariusza albo przeredagowuje Then. Sygnał: strażnik ścieżek pada. Wyjście: odrzuć pull request, zacznij od nowa w świeżej sesji z załadowaną blokadą i powtórz w prompcie zakaz zmiany tagów. Jeśli zdarzy się to dwa razy na jednym scenariuszu, prawdopodobnie scenariusz jest błędny: wróć z nim do product ownera.

Kod kroków słabnie, a plik .feature zostaje bez zmian. Agent zamienia toHaveText('Only 2 in stock') na toBeVisible() w cart.steps.ts. Sygnał: strażnik ścieżek, bo kod kroków leży w features/. Wyjście: trzymaj definicje kroków w zablokowanej ścieżce; zmiana kodu kroków przechodzi przez pull request z etykietą bdd-contract, który CODEOWNERS kieruje do tech leada, a nie do product ownera.

--dry-run zgłasza niezdefiniowane kroki i mimo to kończy się kodem 0. W @cucumber/cucumber 13.2.1 (sprawdzone 2026-09-26) cucumber-js --dry-run zwraca 0 nawet przy niezdefiniowanych krokach, z --strict i bez. Pełny przebieg kończy się kodem 1 przy krokach niezdefiniowanych albo pending. Wyjście: bramką niech będzie pełny przebieg, a dry run służy tylko do szkieletów kodu i raportu usage.

Scenariusze wyglądają jak skrypty klikania. „When I click #add-btn” psuje się przy każdym redesignie i kusi agenta, żeby edytował plik .feature. Wyjście: przepisz kroki na język klienta („adds 2 to the cart”), a selektory trzymaj w kodzie kroków; prompt do example mappingu właśnie dlatego wymaga kroków deklaratywnych.

Implementacja wpisuje przykłady na sztywno. Kod sprawdza "Super Widget". Sygnał: nowy wiersz Examples pada albo recenzent widzi w kodzie aplikacji literały z tabeli. Wyjście: dodaj wiersze przez pull request z kontraktem, a jeden zestaw wierszy trzymaj jako holdout uruchamiany tylko w CI, jak opisuje artykuł o ochronie wyroczni.

Kroki ścigają się z UI i w CI bywają niestabilne (flaky). Sygnał: ludzie restartują job, aż będzie zielony. Wyjście: zastąp page.waitForTimeout asercjami web-first, ustawiaj stan przez testowe API zamiast przez UI i napraw niestabilny test, zanim dodasz kolejne scenariusze. Jeśli niestabilność nie znika, przenieś testy na playwright-bdd, żeby korzystać z trace’ów, fixture’ów i ponowień pojedynczych testów w Playwrighcie (Cucumber.js ma --retry, ale nie ma przeglądarki trace’ów).