Przejdź do głównej zawartości

Wykonywalne kryteria akceptacji: od historyjki do czerwonego testu

Wykonywalne kryteria akceptacji zamieniają każdą linię historyjki użytkownika w scenariusz Given/When/Then z konkretnymi wartościami i automatyczny test, który pada, zanim powstanie jakakolwiek implementacja. Testy zatwierdza człowiek, a potem trafiają poza zasięg edycji agenta implementującego, więc zielony wynik dowodzi uzgodnionego zachowania, a nie interpretacji agenta.

Dajesz agentowi historyjkę: „użytkownicy mogą stosować kody rabatowe; nieprawidłowe kody pokazują błąd”. Czterdzieści minut później pull request jest zielony: 14 nowych testów, wszystkie przechodzą. Żaden nie sprawdza sumy koszyka, wygasły kod zwraca HTTP 200 z komunikatem, a w trzeciej iteracji jeden test po cichu zmienił się z toBe(9000) na toBe(10000). Agent zrobił to, o co go poproszono. Nikt nie zapisał, co znaczy „działa”, w formie, którą maszyna mogłaby wyegzekwować.

Ta strona jest dla programistów, którzy przekazują historyjki do Claude Code, Codeksa lub Cursora, oraz dla tech leadów, którzy chcą przenieść review z diffu na kontrakt zatwierdzany przez zespół, zanim powstanie kod.

  • Wzorzec przepisania mglistej historyjki na trzy do siedmiu numerowanych kryteriów Given/When/Then z konkretnymi wartościami.
  • Czerwone testy akceptacyjne w trzech stosach (TypeScript z Playwrightem, Python z pytest-bdd, Elixir z ExUnit), każdy w wersji „przed” i „po”.
  • Blokadę testów: regułę deny w Claude Code dla sesji implementującej oraz strażnika w CI i wpis w CODEOWNERS, które działają z każdym narzędziem.
  • Cztery prompty do skopiowania: przepytanie historyjki, napisanie czerwonych testów, implementacja pod zablokowany kontrakt i audyt słabości testów.
  • Model akceptacji, w którym człowiek zatwierdza kontrakt i dowody, zamiast czytać każdą zmienioną linię.

Agent optymalizuje pod sprawdzenie, które może uruchomić. Jeśli jedynym sprawdzeniem jest „testy przechodzą”, a testy pisze agent, pętla nie ma punktu stałego: test i kod przesuwają się razem, a zielony kolor znaczy tylko „spójne samo ze sobą”. Kryterium opisane prozą w zgłoszeniu nie pomaga, bo agent czyta je raz, a potem interpretuje.

Rozwiązaniem jest kolejność działań. Kryteria stają się testami przed implementacją, człowiek zatwierdza je, gdy są czerwone, a sesja implementująca nie może ich zmienić. To acceptance test-driven development z jednym dodatkiem, który ma znaczenie przy agentach: autor testów i implementator to osobne sesje, a kontrakt egzekwuje narzędzie, nie instrukcja.

Ta strona dotyczy kontraktu dla jednej historyjki. Utrzymywanie długowiecznego spec.md jako źródła prawdy dla wielu historyjek opisuje spec-driven development, a pisanie i uruchamianie Gherkina z Cucumber.js – przepływy pracy BDD. Miejsce kryteriów w sekwencji intencja → specyfikacja → plan → zadania pokazuje łańcuch artefaktów.

Kryterium jest wykonywalne, gdy test może na nim polec. Prowadzą do tego cztery cechy:

CechaMgliste (przed)Wykonywalne (po)
Konkretne wartości„prawidłowy kod obniża sumꔄkoszyk na 100,00 z kodem SAVE10 (10%) kosztuje 90,00”
Obserwowalne na granicy systemu„rabat jest zapisany”„POST /api/carts/:id/discount zwraca 200 z totalCents: 9000”
Nazwana ścieżka błędu„nieprawidłowe kody pokazują błąd”„wygasły kod zwraca 422 code_expired, a suma zostaje 100,00”
Stały identyfikatorbrakAC-2, użyty w nazwie testu i sprawdzany przez CI

Tak wygląda historyjka o rabatach po przepisaniu. Leży w repozytorium, w specs/discount-codes.md, obok kodu, którym rządzi. Scenariusze zostają po angielsku, bo są wiązane z kodem kroków testów:

# specs/discount-codes.md — approved by: product owner, 2026-09-26
Feature: Discount codes at checkout
# AC-1
Scenario: A valid code takes its percentage off the cart total
Given a cart totalling 100.00
And an active code "SAVE10" worth 10%
When the customer applies "SAVE10"
Then the response is 200
And the cart total is 90.00
# AC-2
Scenario: An expired code is rejected and the total is unchanged
Given a cart totalling 100.00
And a code "SPRING10" that expired on 2026-03-31
When the customer applies "SPRING10"
Then the response is 422 with error "code_expired"
And the cart total is still 100.00
# AC-3
Scenario: A customer can redeem a code only once
Given customer "c-42" has already redeemed "WELCOME15"
And customer "c-42" has a cart totalling 80.00
When they apply "WELCOME15"
Then the response is 409 with error "code_already_used"
And the cart total is still 80.00

Rozsądny zakres to trzy do siedmiu kryteriów na historyjkę. Mniej zwykle oznacza brak ścieżek błędu, więcej – że historyjkę trzeba podzielić (zobacz jak przygotować backlog dla agentów). Przypadki brzegowe, które są regułą dla wielu wejść, np. „suma nigdy nie jest ujemna”, należą do testów opartych na właściwościach, a nie do długiej listy scenariuszy.

Przepływ ma dwie sesje i jedną bramkę ludzką między nimi. Człowiek zatwierdza mały, czytelny artefakt (scenariusze i czerwony przebieg), a nie diff z implementacją.

  1. Przepytaj historyjkę. Daj agentowi zgłoszenie (wklej je albo pobierz przez serwer MCP trackera) i poproś o numerowane kryteria Given/When/Then oraz listę otwartych pytań. Odpowiedz na nie sam albo z product ownerem; agent, który zgaduje odpowiedź, zapisuje zgadywankę w kontrakcie.

  2. Zacommituj kryteria. Zapisz je w specs/<feature>.md razem z identyfikatorami. Ten plik czyta product owner.

  3. Napisz czerwone testy w osobnej sesji. Świeża sesja, bez implementacji w kontekście, pisze po jednym teście na kryterium w tests/acceptance/, nazywa każdy test jego identyfikatorem i steruje systemem przez publiczną granicę (HTTP, CLI albo UI), nigdy przez funkcje wewnętrzne.

  4. Udowodnij, że testy są czerwone z właściwego powodu. Uruchom zestaw akceptacyjny i przeczytaj komunikaty błędów. „Expected 9000, received 404” to poprawna czerwień: trasy jeszcze nie ma. Błąd składni, błąd importu albo brakująca fikstura to zepsuty test, który później zazieleni się z niewłaściwego powodu.

  5. Zatwierdź kontrakt. Otwórz pull request kontraktu z plikiem kryteriów, testami oznaczonymi jako oczekiwane porażki i czerwonym wynikiem wklejonym do opisu. To oznaczenie pozwala, by czerwony z założenia pull request przeszedł wymagane CI; zobacz jak czerwony kontrakt przechodzi przez CI niżej. Zatwierdza go osoba odpowiedzialna za zachowanie (product owner albo tech lead). To krótkie review, bo artefakt jest mały.

  6. Zablokuj kontrakt. Sesja implementująca dostaje regułę deny albo instrukcję dla tests/acceptance/ i specs/, a CI odrzuca każdy pull request z implementacją, który zmienia na tych ścieżkach cokolwiek poza usunięciem oznaczeń oczekiwanej porażki. Pliki są w sekcji o blokadzie poniżej.

  7. Implementuj do zieleni. Zanim sesja wystartuje, właściciel kontraktu (człowiek, nie agent) zakłada gałąź implementacji z jednym commitem, który wyłącznie usuwa oznaczenia oczekiwanej porażki z kroku 5, więc testy są uczciwie czerwone. Dopiero wtedy nowa sesja implementuje pod zablokowane testy i może dopisać tyle własnych testów jednostkowych, ile chce. Kończy, gdy zestaw akceptacyjny przechodzi, albo gdy uzna, że kryterium jest błędne – wtedy raportuje, zamiast edytować.

  8. Sprawdź dowody. CI weryfikuje trzy rzeczy: zestaw akceptacyjny jest zielony, ścieżki kontraktu są niezmienione poza usuniętymi oznaczeniami, a każdy identyfikator kryterium ma test. Recenzent czyta ten wynik, a nie każdą linię diffu.

Jak wygląda czerwony test akceptacyjny w twoim stosie?

Dział zatytułowany „Jak wygląda czerwony test akceptacyjny w twoim stosie?”

Ta sama historyjka w trzech stosach. Każde „przed” to test, jaki agent pisze, sprawdzając po fakcie własną implementację; każde „po” to test na poziomie kryterium, napisany przed jakąkolwiek implementacją.

Przed. Agent zamockował własną funkcję wyszukującą i sprawdził, że mock został wywołany. Test przechodzi niezależnie od tego, czy suma się zmienia:

// src/lib/discounts.test.ts: written by the implementer, after the code
vi.mock('./discount-repo');
it('applies discount', async () => {
vi.mocked(findDiscount).mockResolvedValue({ percent: 10 });
await applyDiscount(cart, 'SAVE10');
expect(findDiscount).toHaveBeenCalledWith('SAVE10');
});

Po. Test API w Playwrighcie (@playwright/test, fikstura request) na granicy HTTP, napisany jako pierwszy:

tests/acceptance/discount-codes.spec.ts
import { test, expect } from '@playwright/test';
import { seedCart, seedCode } from '../support/seed';
test('AC-1: a valid code takes its percentage off the cart total', async ({ request }) => {
const cart = await seedCart(request, { totalCents: 10_000 });
await seedCode(request, { code: 'SAVE10', percent: 10 });
const res = await request.post(`/api/carts/${cart.id}/discount`, { data: { code: 'SAVE10' } });
expect(res.status()).toBe(200);
expect((await res.json()).totalCents).toBe(9_000);
});
test('AC-2: an expired code is rejected and the total is unchanged', async ({ request }) => {
const cart = await seedCart(request, { totalCents: 10_000 });
await seedCode(request, { code: 'SPRING10', percent: 10, expiresAt: '2026-03-31T23:59:59Z' });
const res = await request.post(`/api/carts/${cart.id}/discount`, { data: { code: 'SPRING10' } });
expect(res.status()).toBe(422);
expect((await res.json()).error).toBe('code_expired');
const after = await request.get(`/api/carts/${cart.id}`);
expect((await after.json()).totalCents).toBe(10_000);
});

Przykład zakłada use.baseURL (i blok webServer) w playwright.config.ts; bez tego względne adresy URL padają z błędem nieprawidłowego URL-a, czyli czerwienią z niewłaściwego powodu.

Wynik przed implementacją: Expected: 200, Received: 404. To poprawna czerwień.

Testy „po” mają trzy wspólne nawyki: nazywają kryterium, przygotowują stan przez fikstury zamiast mockować aplikację i sprawdzają wynik, który widzi klient. Test wywołujący tylko funkcje wewnętrzne może przechodzić, gdy endpoint zwraca 500.

Jak trzymać testy akceptacyjne poza zasięgiem edycji agenta?

Dział zatytułowany „Jak trzymać testy akceptacyjne poza zasięgiem edycji agenta?”

Same instrukcje nie wystarczą. Agent, który długo iteruje nad czerwonym testem, w końcu uzna, że „test jest zły”, i go zmieni. Użyj dwóch warstw: blokady w sesji implementującej tam, gdzie narzędzie ją wspiera, i strażnika w CI, który działa niezależnie od narzędzia. Pełne omówienie, łącznie ze scenariuszami holdout trzymanymi poza repozytorium, znajdziesz w artykule ochrona wyroczni.

Dodaj strażnika w CI i właścicieli kodu (wszystkie narzędzia)

Dział zatytułowany „Dodaj strażnika w CI i właścicieli kodu (wszystkie narzędzia)”

Strażnik w CI sprawia, że kontrakt jest wiążący. Jeśli pull request nie ma etykiety zmiany kontraktu, strażnik odrzuca każdą zmianę na ścieżkach kontraktu poza usunięciem oznaczeń oczekiwanej porażki, a CODEOWNERS kieruje do właściciela każdy pull request, który tych ścieżek dotyka:

.github/workflows/acceptance.yml
name: acceptance
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
push:
branches: [main]
permissions:
contents: read
jobs:
contract:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Implementation PRs may only remove expected-failure markers
if: ${{ !contains(github.event.pull_request.labels.*.name, 'acceptance-contract') }}
env:
BASE_REF: ${{ github.base_ref }}
run: |
base=$(git merge-base "origin/$BASE_REF" HEAD)
changes=$(git diff --name-status "$base" HEAD -- specs tests/acceptance)
bad=0
while IFS=$'\t' read -r status path; do
[ -n "$status" ] || continue
if [ "$status" = M ] && [[ "$path" == tests/acceptance/* ]] &&
git show "$base:$path" |
sed -E -e '/^[[:space:]]*@xfail[[:space:]]*$/d' -e 's/@xfail[[:space:]]+//g' -e 's/\btest\.fail\(/test(/g' |
cmp -s - <(git show "HEAD:$path"); then
echo "$path: expected-failure markers removed, nothing else"
else
echo "::error::$status $path changes the contract (or leaves a marker in place)"; bad=1
fi
done <<< "$changes"
if [ "$bad" -ne 0 ]; then
echo "::error::Open a separate PR labeled acceptance-contract for the owner to approve."
exit 1
fi
- name: Every criterion has a test
run: |
for id in $(grep -ohE '([A-Z]+-)?AC-[0-9]+' specs/*.md | sort -u); do
grep -rqE "(^|[^A-Z-])$id([^0-9]|$)" tests/acceptance || { echo "::error::$id has no acceptance test"; exit 1; }
done
open-contracts:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: No expected-failure markers left on the default branch
run: |
if grep -rnE '\btest\.fail\(|@xfail\b' tests/acceptance; then
echo "::error::A contract is merged but not implemented; its markers are listed above."
exit 1
fi
# .github/CODEOWNERS
/specs/ @your-org/product-owners
/tests/acceptance/ @your-org/product-owners
/pytest.ini @your-org/product-owners
/.github/ @your-org/product-owners

Włącz Require review from Code Owners w regule ochrony gałęzi albo w rulesecie dla gałęzi domyślnej; bez tego CODEOWNERS tylko prosi o review. Zamień main na nazwę swojej gałęzi domyślnej. Sam zestaw akceptacyjny uruchamiaj w tym samym workflow albo w istniejącym jobie testowym. Identyfikatory powyżej są unikalne w całym repozytorium; jeśli numerujesz je od nowa w każdej funkcji, dodaj prefiks (DISC-AC-2). Sprawdzenie dopasowuje cały identyfikator, więc test DISC-AC-2 nie spełnia kryterium AC-2 i odwrotnie.

Typy zdarzeń labeled i unlabeled uruchamiają strażnika ponownie, gdy właściciel doda etykietę, więc nikt nie musi wypychać pustego commita. Etykieta tylko kieruje pull request: każdy, kto może dodawać etykiety, także agent z tokenem gh, może sprawić, że strażnik zostanie pominięty. Prawdziwą kontrolą jest Require review from Code Owners na ścieżkach kontraktu. To samo dotyczy samego strażnika: przy zdarzeniu pull_request GitHub uruchamia plik workflow z gałęzi pull requesta, więc pull request z implementacją może strażnika zmienić albo usunąć, a przebieg i tak będzie zielony. Dlatego /.github/ też jest w CODEOWNERS: przy wymaganym review właściciela zmieniony workflow nie wejdzie bez jego zgody. Workflow nie potrzebuje sekretów, ma token tylko do odczytu i pobiera kod z persist-credentials: false.

Pull request kontraktu jest czerwony z założenia, więc jeśli zestaw akceptacyjny jest wymaganym checkiem, nie da się go scalić. Nie rozwiązuj tego, robiąc job akceptacyjny opcjonalnym na gałęzi domyślnej: opcjonalny check to także furtka, przez którą pull request z implementacją wejdzie na czerwono. Dwa wzorce zostawiają check wymagany.

Ścisłe oznaczenia oczekiwanej porażki (Playwright, pytest). Pull request kontraktu deklaruje każdy nowy test jako taki, który ma się nie udać. Wymagany job przechodzi wtedy tylko dopóty, dopóki te testy naprawdę padają, więc CI przy każdym pushu ponownie dowodzi czerwonego przebiegu. Gdy implementacja sprawi, że test przejdzie, runner zgłosi nieoczekiwany sukces jako porażkę:

// In the contract PR: same title, so the ID check still finds it
test.fail('AC-1: a valid code takes its percentage off the cart total', async ({ request }) => {
// ...body unchanged...
});
RunnerOznaczenie w pull requeście kontraktuWynik, gdy funkcja już działa
Playwrighttest.fail('AC-1: ...', async () => ...)Expected to fail, but passed., job pada
pytest-bddtag @xfail na scenariuszu oraz xfail_strict = true w sekcji [pytest] pliku pytest.iniXPASS(strict), job pada

Usunięcie oznaczeń to krok człowieka i idzie na początek. Dopóki oznaczenie jest na miejscu, poprawna implementacja zmienia zestaw na czerwony (Expected to fail, but passed.), co agenta mającego dojść do zieleni popchnęłoby do cofnięcia działającej funkcji. Dlatego zanim sesja implementująca wystartuje, właściciel kontraktu zakłada gałąź implementacji z jednym commitem, który wyłącznie usuwa oznaczenia tej historyjki (test.fail( z powrotem na test(, tagi @xfail). Sesja pracuje wtedy pod uczciwie czerwone testy i kończy, gdy zestaw jest zielony, tak jak mówią prompty na tej stronie. Taki pull request nie potrzebuje etykiety: strażnik porównuje każdy zmieniony plik w tests/acceptance/ z jego wersją bazową bez oznaczeń i odrzuca każdą inną różnicę, także pozostawione oznaczenie, więc trzymaj jedną historyjkę w jednym pliku testów. CODEOWNERS i tak kieruje go do właściciela, który widzi diff testów złożony wyłącznie z usuniętych oznaczeń. Oba runnery sprawdzono 2026-10-02 (Playwright 1.63.0, pytest 9.1.1 z pytest-bdd 9.0.0): job był zielony, dopóki testy padały, i padał, gdy zaślepiona funkcja zwracała oczekiwaną wartość. Skrypt strażnika uruchomiono na zmianach: tylko usunięte oznaczenia, usunięcie częściowe, osłabiona asercja, dodany tag, nowy plik, usunięty plik i zmiana specyfikacji; przeszły tylko zmiana samych oznaczeń i zmiana samego kodu aplikacji.

Ścisłość też trzymaj poza zasięgiem implementacji. Przy xfail_strict = false w pytest.ini otagowany scenariusz zgłasza 1 xpassed, gdy funkcja działa, i 1 xfailed, gdy jest zepsuta, w obu przypadkach z kodem wyjścia 0, więc oznaczeń nigdy nie trzeba usuwać. Uruchamiaj zestaw pytest w CI jako pytest -o xfail_strict=true tests/acceptance; nadpisanie z linii poleceń wygrywa z plikiem ini (przy ini ustawionym na false działająca funkcja daje 1 failed i kod wyjścia 1). Z tego samego powodu pytest.ini jest w CODEOWNERS powyżej. Job open-contracts pada na gałęzi domyślnej, dopóki zostaje tam jakiekolwiek oznaczenie. Nie blokuje żadnego pull requesta; sprawia, że kontrakt scalony, ale jeszcze niezaimplementowany, jest widoczny jako czerwony przebieg, dopóki pull request z implementacją nie usunie jego oznaczeń.

ExUnit nie ma ścisłego trybu oczekiwanej porażki, a zwykłe wykluczenie przez @tag :pending niczego nie pilnuje (implementacja wchodzi do gałęzi, a testy ani razu się nie uruchamiają), więc projekty w Elixirze używają drugiego wzorca.

Gałąź historyjki. Pull request kontraktu celuje w story/discount-codes zamiast w gałąź domyślną, a pull requesty z implementacją celują w gałąź historyjki, więc strażnik porównuje zmiany z bazą, która już zawiera kontrakt. Daj gałęziom story/* własny ruleset z Require review from Code Owners, bo inaczej pull request kontraktu wejdzie bez właściciela. W tym rulesecie wymagaj strażnika (jobu contract), ale nie zestawu akceptacyjnego: pull request kontraktu do gałęzi historyjki jest czerwony z założenia, a wymagany zestaw zablokowałby go dokładnie tak samo jak na gałęzi domyślnej. Zestaw jest wymagany przy końcowym pull requeście z gałęzi historyjki do gałęzi domyślnej, który musi być zielony i dostaje etykietę acceptance-contract, bo wnosi ze sobą ścieżki kontraktu. Oznaczenia nie są tu potrzebne, więc sesja implementująca od razu pracuje pod czerwone testy.

Claude Code sprawdza reguły Edit(path) dla każdego wbudowanego narzędzia zapisującego pliki, dla rozpoznawanych poleceń plikowych w Bashu, takich jak sed i tee, oraz dla celów przekierowań. Reguły deny z dowolnego zakresu ustawień wygrywają z regułami allow. Reguła we współdzielonym .claude/settings.json zablokowałaby też sesję piszącą testy, więc przekaż reguły jako flagi tylko sesji implementującej:

Okno terminala
# Terminal: interactive implementation in its own worktree
claude --worktree disc-impl \
--disallowedTools "Edit(/specs/**)" "Edit(/tests/acceptance/**)"
# Terminal or CI: headless implementation with a budget
claude -p "Implement specs/discount-codes.md until npx playwright test tests/acceptance passes." \
--disallowedTools "Edit(/specs/**)" "Edit(/tests/acceptance/**)" \
--permission-mode acceptEdits \
--allowedTools "Bash(npx playwright test *)" \
--max-budget-usd 5

Wiodący / zakotwicza wzorzec w źródle ustawień. Dla flag CLI oraz dla .claude/settings.json i .claude/settings.local.json jest nim główny katalog roboczy (tutaj worktree). Dla pliku przekazanego przez --settings <plik> jest nim katalog tego pliku, więc te same reguły w .claude/implementer.json blokowałyby .claude/specs/** i niczego by nie chroniły; jeśli wolisz plik, użyj w nim ścieżek bezwzględnych z //. Strona uprawnień Anthropic ostrzega, że te reguły nie obejmują „arbitrary subprocesses that read or write files indirectly, like a Python or Node script”; do egzekwowania na poziomie systemu użyj sandboksa Basha, a wiążącym sprawdzeniem niech zostanie strażnik w CI. Flagi sprawdzone na Claude Code 2.1.283, 2026-09-26.

Jeśli historyjka żyje w Linearze albo Jirze, podłącz tracker, żeby krok 1 czytał zgłoszenie bezpośrednio: claude mcp add --transport http linear https://mcp.linear.app/mcp w Claude Code, codex mcp add linear --url https://mcp.linear.app/mcp && codex mcp login linear w Codeksie (codex mcp login linear przeprowadza logowanie OAuth wymagane przez serwer Lineara) i zdalny wpis pod mcpServers w .cursor/mcp.json w Cursorze. Przy kryteriach dotyczących UI serwer MCP Playwrighta (@playwright/mcp) pozwala sesji piszącej testy obejrzeć działającą stronę, zanim wybierze selektory; konfigurację opisują serwery MCP do automatyzacji przeglądarki.

Sens kontraktu polega na tym, że nikt nie musi czytać każdej zmienionej linii, żeby ufać wynikowi. Każde sprawdzenie ma właściciela i sygnał:

Co jest sprawdzaneJakKto zatwierdza
Kryteria opisują właściwe zachowaniePull request kontraktu: plik kryteriów, nazwy testów, czerwony wynikProduct owner albo tech lead, przed implementacją
Testy są czerwone z właściwego powoduCzerwony przebieg wklejony do pull requesta kontraktuTen sam recenzent
Testy są wystarczająco silnePrompt audytowy powyżej, potem wynik mutacyjny z artykułu jak silna jest twoja wyroczniaTech lead, dla całego zestawu, a nie dla każdego pull requesta
Implementacja spełnia kontraktZielony zestaw akceptacyjny w CICI
Kontraktu nie dopasowano do koduStrażnik w CI na specs/ i tests/acceptance/ (bez etykiety przechodzi tylko usunięcie oznaczeń), CODEOWNERS na tych ścieżkach, pytest.ini i .github/CI oraz właściciel kodu przy usunięciu oznaczeń i każdej zmianie kontraktu
Każde kryterium ma pokrycieKrok sprawdzający identyfikatoryCI

Pull request z implementacją niesie potem krótkie podsumowanie dowodów: identyfikatory AC z wynikami, wynik strażnika i wszystko, co zgłosił agent. To podsumowanie jest jednym polem pakietu dowodów, a czytanie go zamiast diffu to praktyka opisana w artykule dowody zamiast diffów. Recenzent nadal czyta kod tam, gdzie wymaga tego klasa ryzyka (bezpieczeństwo, płatności, migracje); kontrakt zmienia domyślny tryb, a nie każdy przypadek.

Co się psuje przy wykonywalnych kryteriach akceptacji?

Dział zatytułowany „Co się psuje przy wykonywalnych kryteriach akceptacji?”

Agent edytuje albo pomija test. Sygnał: strażnik w CI pada albo w katalogu akceptacyjnym pojawia się .skip lub @pytest.mark.skip. Naprawa: odrzuć pull request, uruchom implementację od nowa w świeżej sesji z załadowaną blokadą i dopisz „do not skip” do promptu implementacyjnego. Jeśli dzieje się to dwa razy przy tym samym kryterium, kryterium jest prawdopodobnie błędne; wróć z nim do pull requesta kontraktu.

Agent robi wyjątek dla danych testowych. Kod sprawdza if (code === 'SAVE10'). Sygnał: prompt audytowy albo recenzent szukający literałów z testów. Naprawa: przez pull request kontraktu dodaj do scenariusza drugi przykład z innymi wartościami (scenario outline albo drugi test) i trzymaj jeden zestaw przykładów poza repozytorium jako holdout uruchamiany tylko w CI; zobacz ochrona wyroczni.

Testy były czerwone z niewłaściwego powodu. Brakująca fikstura albo literówka sprawiły, że wszystkie testy padały, a implementator „naprawił” kod wspierający testy, aż zrobiło się zielono. Naprawa: powtórz krok 4 i przeczytaj każdy komunikat błędu przed zatwierdzeniem; traktuj tests/support/ jako część kontraktu, jeśli implementator nie powinien go zmieniać.

Testy są sprzężone z implementacją. Importują klasę serwisu albo sprawdzają zapytanie SQL, więc każdy refaktoring je psuje, a agent ma pokusę, żeby je edytować. Naprawa: przepisz je na publiczną granicę. Jeśli zachowanie nie ma jeszcze publicznej granicy, kryterium jest na złym poziomie; przenieś je do testu jednostkowego, którego właścicielem jest implementator.

Kontrakt zmienia się w trakcie historyjki. Produkt zmienia regułę, gdy implementacja trwa. Naprawa: zatrzymaj przebieg, otwórz pull request kontraktu ze zmienionym kryterium i jego nowym czerwonym testem, zatwierdź go i dopiero wtedy wznów implementację. Nigdy nie pozwól, żeby zmianę wniosła sesja implementująca.

Zestaw akceptacyjny robi się wolny albo niestabilny. Sygnał: programiści restartują CI, aż będzie zielono. Naprawa: trzymaj trzy do siedmiu testów akceptacyjnych na historyjkę i, gdzie się da, na granicy API; przypadki kombinatoryczne przenieś do testów jednostkowych albo testów właściwości; naprawiaj niestabilne testy, zanim dodasz scenariusze, bo niestabilna wyrocznia uczy wszystkich, ludzi i agentów, ignorować czerwień.

Najczęstsze pytania

Czym jest wykonywalne kryterium akceptacji?

To kryterium zapisane jako scenariusz Given/When/Then z konkretnymi wartościami, sparowane z automatycznym testem, który nie przechodzi, zanim funkcja powstanie, i przechodzi dopiero wtedy, gdy zachowanie istnieje. Ma identyfikator, np. AC-2, dzięki któremu CI sprawdza, że każde kryterium ma test.

Dlaczego testy akceptacyjne muszą być poza zasięgiem edycji agenta?

Agent, któremu każesz doprowadzić testy do zieleni, może to zrobić, zmieniając testy. Jeśli sesja implementująca nie może ich edytować, a CI odrzuca każdy pull request z implementacją, który zmienia w nich coś więcej niż usunięcie oznaczeń oczekiwanej porażki, zielony wynik oznacza, że zatwierdzone zachowanie naprawdę istnieje.

Co zatwierdza człowiek, jeśli nie diff?

Kontrakt: scenariusze, nazwy testów powiązane z identyfikatorami kryteriów i czerwony przebieg, który pokazuje, że każdy test pada z właściwego powodu. Po implementacji człowiek sprawdza dowody (zielony przebieg akceptacyjny, niezmieniony kontrakt, pokrycie identyfikatorów), a nie czyta każdej linii.