TDD z agentami AI: najpierw czerwony test
Programowanie sterowane testami (TDD) z agentami AI oznacza, że zatwierdzony przez człowieka, nieprzechodzący test istnieje i pada z właściwego powodu, zanim agent napisze jakąkolwiek implementację. Agent zmienia potem kod aplikacji, aż zestaw testów przejdzie, przy zablokowanych plikach testów. Tak samo naprawia się błędy: odtwórz defekt czerwonym testem, zacommituj go, a potem napraw kod.
Ta strona jest dla programistów, którzy na co dzień prowadzą Claude Code, Codeksa albo Cursora, i dla tech leadów ustalających pętlę dla całego zespołu. Prosisz agenta o helper do kuponów. Dostajesz kod i zestaw testów, który przechodzi, a dwa dni później dział finansów zgłasza, że wygasłe kupony wciąż dają rabat. Testy powstały po kodzie, więc dowiodły tylko tego, że kod robi to, co robi kod.
Co daje ci TDD prowadzone z agentem
Dział zatytułowany „Co daje ci TDD prowadzone z agentem”- Czterofazową pętlę (czerwony, potwierdzenie czerwonego, zielony, refaktoryzacja), w której każda faza to osobny, wąski prompt
- Gotowe do wklejenia prompty na każdą fazę oraz szablon zamieniający listę wymagań w specyfikację testów
- Protokół naprawy błędów od testu: test reprodukujący, commit czerwonego stanu, zablokowane testy, zweryfikowany zielony przebieg
- Skrypt, który dowodzi, że czerwony test padał przed poprawką i że żaden test nie zmienił się potem
- Objawy tego, że agent osłabia testy, by wymusić zielony wynik, i sposób wyjścia z tej sytuacji
Dlaczego test musi powstać pierwszy, gdy kod pisze agent?
Dział zatytułowany „Dlaczego test musi powstać pierwszy, gdy kod pisze agent?”Agent optymalizuje pod cel, który mu dasz. Powiedz „zaimplementuj funkcję”, a „gotowe” oznacza to, co agent uzna za gotowe. Daj mu nieprzechodzący test, a „gotowe” staje się sprawdzeniem, które agent może uruchomić i z którym nie może dyskutować. To sedno weryfikowania zachowania zamiast czytania każdego diffa: przeglądasz test, który jest krótki i wyraża intencję, a o implementacji rozstrzyga zestaw testów.
Zespół DORA z Google Cloud napisał to samo o dostarczaniu wspieranym przez AI w raporcie z 2025 roku (2025-09-23): „Without robust control systems, like strong automated testing, mature version control practices, and fast feedback loops, an increase in change volume leads to instability”. Nieprzechodzący test, który istnieje przed kodem, to najmniejsza jednostka takiego systemu kontroli.
Kolejność ma znaczenie jeszcze z jednego powodu. Gdy ta sama tura pisze i test, i kod, agent zwykle pisze test potwierdzający to, co robi jego implementacja, łącznie z błędami. Rozdzielenie tur i zatwierdzenie testów przez człowieka pomiędzy nimi sprawia, że zielony przebieg w ogóle coś znaczy.
Pętla red-green-refactor z agentem
Dział zatytułowany „Pętla red-green-refactor z agentem”Klasyczny cykl to czerwony, zielony, refaktoryzacja. Z agentem każda faza staje się osobną instrukcją, a całość trzyma jedna reguła: tura, która pisze nieprzechodzący test, nigdy nie pisze kodu, który go spełnia.
- Napisz testy (czerwony). Nazwij zachowania i przypadki brzegowe, zabroń implementacji. Opisujesz kontrakt, a nie zamawiasz funkcję. Zacznij od trzech do pięciu testów podstawowego zachowania; przypadki brzegowe dodaj, gdy pierwszy wycinek będzie zielony.
- Potwierdź czerwony. Agent uruchamia zestaw i pokazuje niepowodzenia. Każde musi dotyczyć brakującego zachowania, a nie literówki w imporcie czy zepsutej fixtury. Tu zatwierdzasz testy: to twój przegląd specyfikacji.
- Zacommituj czerwony stan. Zacommituj same pliki testów (
test: …). Commit jest dowodem, że test istniał i padał przed jakąkolwiek poprawką, oraz punktem, do którego wracasz, jeśli faza zielona pójdzie źle. - Implementuj do zielonego. Jedna instrukcja: spraw, by te testy przeszły, nie ruszaj plików testów. Agent uruchamia zestaw po każdej zmianie i iteruje, aż wszystko będzie zielone, łącznie z resztą zestawu.
- Refaktoryzuj przy zielonym. Poproś o poprawę czytelności, która zmienia tylko implementację. Zablokowane testy są teraz siatką bezpieczeństwa, dzięki której przebudowa jest tania.
Jak zamienić wymagania w specyfikację testów?
Dział zatytułowany „Jak zamienić wymagania w specyfikację testów?”Zacznij od zachowania, nie od implementacji. Konkretne wymagania dają konkretne testy; ogólnikowy prompt daje testy sprawdzające, że funkcja istnieje. Użyj tego szablonu, gdy zaczynasz od dokumentu z wymaganiami i chcesz, żeby agent wyliczył przypadki:
Podmień FEATURE, REQUIREMENTS, TEST_FILE_PATH i EXISTING_TEST_FILE. Oto ten sam szablon wypełniony dla rate limitera w Expressie, gdzie współbieżność to dokładnie ten przypadek, którego agent sam z siebie nie przetestuje:
Przeprowadź cztery fazy na metodzie serwisu
Dział zatytułowany „Przeprowadź cztery fazy na metodzie serwisu”Czysta funkcja daje schludne demo i niewiele uczy. Oto pętla na przypadku z produkcji: metoda serwisu ze ścieżkami błędów i zamockowanym repozytorium.
Faza 1: tylko testy. Przypnij agenta do pisania testów i niczego więcej.
Faza 2: potwierdź czerwony. Nie pomijaj tego kroku. Test, który przechodzi już teraz, niczego nie sprawdza.
Przeczytaj listę niepowodzeń, zatwierdź testy i je zacommituj: git add src/services/pricing.test.ts && git commit -m "test: specify PricingService.applyCoupon".
Faza 3: implementacja do zielonego. Dopiero teraz pozwalasz na implementację i odgradzasz testy.
Pierwsza reguła jest kluczowa. Bez niej agent, który utknie, często „naprawia” asercję zamiast kodu. Limit prób wynika z tej samej logiki co w agentach programistycznych Stripe’a, które, jak napisał Alistair Gray ze Stripe’a 2026-02-09, dostają „at most two rounds of CI”, po czym, jak podaje wpis uzupełniający z 2026-02-19, gałąź wraca do jej ludzkiego operatora: ograniczona pętla pada głośno, zamiast dryfować.
Faza 4: refaktoryzacja przy zielonym.
Naprawiaj błędy od testu: odtwórz, zacommituj, zablokuj, napraw
Dział zatytułowany „Naprawiaj błędy od testu: odtwórz, zacommituj, zablokuj, napraw”Naprawa błędu to ta sama pętla, tyle że test pisze się pod defekt, a nie pod funkcję. Gdy wklejasz stack trace i piszesz „napraw to”, agent często zmienia kod, aż objaw zniknie, i ogłasza sukces, a nic nie dowodzi, że defekt istniał ani że zniknął. Protokół „najpierw test” wymusza ten dowód. To także odpowiedź, za którą Developer Scorecard daje maksimum punktów w pytaniu o naprawę błędów: zacommitowany nieprzechodzący test, blokada plików testów na czas poprawki i zweryfikowany zielony przebieg.
-
Odtwórz błąd nieprzechodzącym testem. Daj agentowi zgłoszenie albo log i zabroń zmian w kodzie źródłowym.
-
Zacommituj czerwony test osobno. Zacznij komunikat od
test: reproducei zapisz SHA (git rev-parse HEAD): podasz je skryptowi weryfikującemu poniżej.Okno terminala # Terminal, katalog główny repozytoriumgit add tests/auth/email-change.test.tsgit commit -m "test: reproduce session invalidation on email change" -
Zablokuj pliki testów na czas poprawki. Użyj blokady dla swojego narzędzia z następnej sekcji. Sama instrukcja w prompcie to prośba, a nie zabezpieczenie.
-
Poproś o poprawkę.
-
Zweryfikuj zielony przebieg skryptem poniżej, a nie czytaniem diffa: reprodukcja padała przed poprawką, przechodzi po niej, cały zestaw jest zielony, a żaden plik testu nie zmienił się po czerwonym commicie.
Pull request z poprawką zbudowany w ten sposób ma co najmniej dwa commity: czerwony test, a potem poprawkę. Test reprodukujący zostaje w zestawie na stałe jako test regresji.
Jak zablokować pliki testów, gdy agent implementuje?
Dział zatytułowany „Jak zablokować pliki testów, gdy agent implementuje?”W każdym prompcie fazy zielonej pisz „do not modify the test files” i podeprzyj to mechanizmem, którego agent nie obejdzie argumentacją. Mechanizmy różnią się między narzędziami; pełną, wielowarstwową konfigurację, łącznie z CODEOWNERS i sprawdzeniem w CI, którego pull request nie może zmienić, znajdziesz na stronie o ochronie wyroczni.
Dodaj reguły deny do .claude/settings.json. Reguły deny blokują w każdym trybie uprawnień, a reguła Edit(...) obejmuje też Write:
{ "permissions": { "deny": ["Edit(tests/**)", "Edit(**/*.test.ts)", "Edit(**/__snapshots__/**)"] }}Reguły deny nie powstrzymają skryptu uruchomionego przez agenta przed zapisem plików. Do tego potrzebujesz filesystem.denyWrite w sandboksie i hooka PreToolUse, który mówi Claude’owi, co zrobić zamiast edycji testu; oba są na stronie o wyroczni. Hook blokuje tylko wtedy, gdy kończy się kodem 2; kod 1 przepuszcza edycję.
Użyj profilu uprawnień (beta, Codex CLI 0.138.0 lub nowszy), który w sandboksie ustawia katalog testów jako tylko do odczytu. W ~/.codex/config.toml:
[permissions.locked-tests]extends = ":workspace"
[permissions.locked-tests.filesystem.":project_roots"]"tests" = "read"Wybierz go dla przebiegu przez codex exec -c default_permissions=locked-tests "…". W Codex CLI 0.157.1 glob taki jak "**/*.test.ts" przyjmuje tylko dostęp deny, który blokuje też odczyt, więc testy leżące obok kodu wymagają sprawdzenia w CI opisanego niżej. Nie łącz profilu z --sandbox ani --approve-for-me: OpenAI pisze, że profile i starszy system sandboksa „do not compose”. Wpisz też regułę do AGENTS.md, żeby agent wiedział, dlaczego zapis się nie udał.
Dodaj regułę projektu (lokalizację i format pliku podaje dokumentacja Rules Cursora) o treści „Never modify test files during an implementation task; if a test looks wrong, stop and explain.” Potem dodaj hook Cursora blokujący edycję plików pasujących do twoich globów testów: hooki Cursora uruchamiają się przed wybranymi etapami pętli agenta lub po nich i mogą je obserwować, blokować albo modyfikować (dokumentacja: „run before or after defined stages of the agent loop and can observe, block, or modify behavior”, sprawdzone 2026-08-28). Nazw zdarzeń hooków nie udało się ponownie sprawdzić 2026-09-26, więc weź je z dokumentacji hooków Cursora.
Blokada na twoim laptopie nie przechodzi na Cloud Agents Cursora, które działają we własnych maszynach wirtualnych. Dla nich egzekwowaniem jest sprawdzenie w CI opisane niżej.
Skąd wiesz, że zielony wynik jest prawdziwy, bez czytania kodu?
Dział zatytułowany „Skąd wiesz, że zielony wynik jest prawdziwy, bez czytania kodu?”Nie musisz czytać implementacji linijka po linijce, żeby jej zaufać. Potrzebujesz czterech faktów, a skrypt ustali każdy z nich:
- Test reprodukujący albo specyfikujący padał na czerwonym commicie.
- Przechodzi na
HEAD. - Cały zestaw przechodzi na
HEAD. - Żaden plik testu nie zmienił się między czerwonym commitem a
HEAD.
#!/usr/bin/env bash# scripts/verify-tdd.sh: uruchamiany w terminalu albo w CI na gałęzi z poprawką lub TDDset -euo pipefailTEST_FILE=${1:?usage: verify-tdd.sh <test file> <red commit sha>}RED=$(git rev-parse --verify "${2:?usage: verify-tdd.sh <test file> <red commit sha>}^{commit}")git merge-base --is-ancestor "$RED" HEAD || { echo "FAIL: $RED is not an ancestor of HEAD"; exit 1; }git cat-file -e "$RED:$TEST_FILE" || { echo "FAIL: $TEST_FILE does not exist at $RED"; exit 1; }ORIG=$(git symbolic-ref -q --short HEAD || git rev-parse HEAD)
changed=$(git diff --name-only "$RED" HEAD -- 'tests/' '*.test.ts' '*.spec.ts' \ '**/__snapshots__/**' 'vitest.config.*' 'jest.config.*')if [ -n "$changed" ]; then echo "FAIL: test files or test config changed after the red commit:"; echo "$changed"; exit 1fi
trap 'git checkout --quiet "$ORIG"' EXITgit checkout --quiet --detach "$RED"if npx vitest run "$TEST_FILE" >/dev/null 2>&1; then echo "FAIL: $TEST_FILE passes without the fix, so it does not reproduce anything"; exit 1figit checkout --quiet "$ORIG"
npx vitest run "$TEST_FILE"npx vitest runecho "PASS: red before the fix, green after it, tests untouched"Czerwony commit podajesz jawnie: scripts/verify-tdd.sh src/services/pricing.test.ts <red-sha>, czyli SHA zatwierdzone przez człowieka przy potwierdzaniu fazy czerwonej. Skrypt celowo nie szuka go w opisach commitów: gdyby brał ostatni commit test: , późniejszy commit osłabiający test pod tym samym prefiksem stałby się nowym punktem odniesienia, a sprawdzenie „żaden plik testów się nie zmienił” przeszłoby właśnie przy zmianie, którą ma wyłapywać. Skrypt wymaga czystego drzewa roboczego (najpierw commit albo stash). Kończy się błędem, jeśli czerwony commit nie jest przodkiem HEAD, więc SHA z innej gałęzi, na której test akurat pada z niezwiązanych powodów, nie może udawać punktu odniesienia, a sprawdzenie różnic zawsze porównuje historię tej samej gałęzi. Kończy się błędem, jeśli pliku testu nie było na czerwonym commicie, bo Vitest zwraca niezerowy kod także wtedy, gdy nie znajdzie żadnego pliku testu, więc literówka liczyłaby się jako „czerwony”. Sprawdzenie „żaden plik testów się nie zmienił” obejmuje też snapshoty oraz vitest.config.* / jest.config.*, bo wykluczenie testu w konfiguracji albo przepisanie snapshotu osłabia zestaw tak samo jak edycja testu. trap przywraca twoją gałąź albo odłączony commit, który pobrało CI, nawet gdy któryś krok się nie powiedzie. Wzorce ścieżek w git diff, takie jak '*.test.ts', pasują na dowolnej głębokości. Dostosuj ścieżki i runner do swojego stosu: pytest i go test działają tak samo, bo sprawdzenie opiera się wyłącznie na kodzie wyjścia.
Kto co zatwierdza:
| Artefakt | Kto zatwierdza | Dowód |
|---|---|---|
| Lista testów i asercje | Człowiek, na kroku potwierdzenia czerwonego | Wynik niepowodzeń i czerwony commit |
| Implementacja | Zestaw testów, nie czytelnik | verify-tdd.sh przechodzący w CI |
| Każda zmiana testu po czerwonym commicie | Człowiek, w osobnym pull requeście | Właściciel plików testów w CODEOWNERS |
Zielony zestaw dowodzi tylko tego, co sprawdzają testy. Żeby się dowiedzieć, czy złapałyby prawdziwy błąd, zmierz siłę wyroczni testami mutacyjnymi na zmienionym kodzie i dodaj testy oparte na właściwościach tam, gdzie przestrzeni wejść nie da się wyliczyć ręcznie.
Prowadź pętlę w Claude Code, Codeksie i Cursorze
Dział zatytułowany „Prowadź pętlę w Claude Code, Codeksie i Cursorze”Fazy są identyczne w każdym narzędziu. Różni się to, jak agent uruchamia zestaw i jaką część pętli test–poprawka–test wykonuje bez nadzoru.
W sesji interaktywnej wklejaj prompty kolejnych faz. Wpisz dokładne polecenia testów do CLAUDE.md (jeden plik, testy powiązane, cały zestaw), żeby Claude nie zgadywał między npm test a npx vitest. Żeby „gotowe” znaczyło „zielone”, dodaj hook Stop, który uruchamia zestaw i kończy się kodem 2, dopóki testy padają; przetestowaną wersję znajdziesz w przewodniku po hookach.
Skryptową fazę zieloną uruchom w trybie headless, tylko z potrzebnymi narzędziami:
# Terminal albo CI (Claude Code 2.1.283)claude -p "Implement src/services/pricing.ts so every test in src/services/pricing.test.ts passes. Run 'npx vitest run' and iterate until green. Do not edit any *.test.ts file." \ --permission-mode acceptEdits \ --allowedTools "Read,Edit,Write,Bash(npx vitest *)"Jeśli Claude krąży wokół tego samego niepowodzenia, użyj /clear i zacznij nową sesję od promptu, który podaje jeden padający test i błąd, a nie całe zadanie.
Każdą fazę prowadź w osobnym wątku, żeby rozmowa o pisaniu testów nie zaśmiecała kontekstu implementacji. Skryptowa faza zielona:
# Terminal albo CI (Codex CLI 0.157.1)codex exec -c default_permissions=locked-tests \ "Implement src/services/pricing.ts so every test in src/services/pricing.test.ts passes. Run 'npx vitest run' and iterate until green. Do not touch the test files."locked-tests to profil z poprzedniej sekcji; bez niego użyj -c default_permissions=":workspace". Padający test to zwykły wynik polecenia, a nie zdarzenie wymagające zgody, więc pętla działa bez pytania cię o nic. Żeby wypróbować dwie implementacje pod te same zablokowane testy, uruchom każdą we własnym worktree (codex exec --worktree …), aby przebiegi się nie zderzały.
Użyj Agenta w IDE i pozwól mu samemu uruchamiać testy. To, czy polecenie testów uruchomi się bez pytania, zależy od trybu uruchamiania, który Cursor opisuje na stronie Run modes w sekcji Agent security. Nazw trybów nie udało się ponownie sprawdzić 2026-09-26, więc wybierz tryb według tej strony; w pętli bez nadzoru nigdy nie wybieraj trybu, który pomija zabezpieczenia Cursora.
Każdą fazę prowadź w osobnym czacie, a do fazy czerwonej użyj Plan Mode, gdy chcesz dostać listę testów, zanim powstanie jakikolwiek plik. Pilnuj widoku diffa: jeśli edycja w fazie zielonej dotyka pliku *.test.ts, odrzuć ją i powtórz regułę. Ponieważ czerwony stan jest zacommitowany, git reset --hard do tego commita zawsze jest czystą drogą powrotu.
O wyborze modelu: zacznij od domyślnego modelu narzędzia (Claude Opus 5.5 w Claude Code od v2.1.280 na kanale latest, GPT-6 Astra w Codeksie) i zwiększ effort, zanim zmienisz model; w Claude Code pierwszą dźwignią przy trudnej fazie zielonej jest /effort high. Claude Fable 5.1 (/model fable) wybieraj świadomie do najdłuższych i najtrudniejszych implementacji. Ceny i kompromisy znajdziesz w przeglądzie modeli.
Użyj skilla TDD zamiast wklejać reguły za każdym razem
Dział zatytułowany „Użyj skilla TDD zamiast wklejać reguły za każdym razem”Jeśli prowadzisz tę pętlę codziennie, zainstaluj ją jako skill, żeby agent trzymał się jej bez długiego promptu. Dwa są szeroko instalowane, mają łącznie około 967 000 i 237 000 instalacji w skills.sh według zestawienia LinklyAI/best-skills z 2026-09-26 (źródło wtórne): tdd Matta Pococka (red-green-refactor po jednym pionowym wycinku, testowanie na uzgodnionych „szwach”) i skill test-driven-development z Superpowers (ścisłe red-green-refactor, zapisane jako „Iron Law”).
# Terminal, katalog główny repozytorium: jedno polecenie instaluje skill dla trzech narzędzinpx skills add mattpocock/skills --skill tdd -a claude-code -a codex -a cursornpx skills add obra/superpowers --skill test-driven-development -a claude-code -a codex -a cursorSkill zmienia to, co agent próbuje zrobić; niczego nie blokuje. Zachowaj reguły deny, profil albo hook oraz skrypt weryfikujący. Porównanie tych skilli i instalację jako pluginy znajdziesz na stronie skille, które uczą agenta porządnie testować i debugować.
Co się psuje, gdy prowadzisz TDD z agentem?
Dział zatytułowany „Co się psuje, gdy prowadzisz TDD z agentem?”- Testy przechodzą, zanim kod istnieje. Zamockowana zależność domyślnie zwraca wartość truthy albo import trafia w starą zaślepkę. Co zrobić: nigdy nie pomijaj potwierdzenia czerwonego; jeśli wynik jest zielony, usuń test i napisz go od nowa pod publiczny interfejs.
- Agent osłabia test, żeby wymusić zielony wynik. Rzadko wygląda to dramatycznie:
expect(res.status).toBe(429)zmienia się wtoBeDefined(),toBe(expected)wtoBeTruthy(), „niestabilny” test znika albo mock zastępuje dokładnie to zachowanie, które test miał sprawdzać. Co zrobić:git checkout <red-commit> -- tests/, żeby przywrócić testy, dodaj blokadę dla swojego narzędzia i uruchom prompt fazy zielonej ponownie. Zestaw, który się skurczył, to sygnał alarmowy. - Test i poprawka w tej samej turze. Agent pisze test, który potwierdza jego własne błędne zachowanie. Co zrobić: wyrzuć jedno i drugie i zacznij od fazy czerwonej w nowej sesji.
- Testy są trywialne. „Write tests for this function” daje testy sprawdzające, że funkcja istnieje. Co zrobić: nazwij każde zachowanie i podaj w prompcie konkretne pary wejście–wyjście.
- Testy są sklejone z implementacją. Sprawdzają prywatne metody albo wewnętrzne struktury danych, więc każda refaktoryzacja je psuje. Co zrobić: przepisz je pod publiczne API; skill
tddPococka kieruje agenta w tę stronę, testując na uzgodnionych „szwach”. - Niestabilne testy asynchroniczne „naprawiane” przez sleep. Co zrobić: poproś o deterministyczną kontrolę („use
vi.useFakeTimers()and advance time explicitly; do not add real delays”). - Zestaw jest zbyt wolny, żeby na nim iterować. Agent czeka minutami na każdą próbę i zużywa kontekst. Co zrobić: w pętli uruchamiaj tylko testowany plik (
npx vitest run src/services/pricing.test.ts; Jest od wersji 30 używa--testPathPatterns), a cały zestaw raz na końcu. - Poprawka psuje sąsiednie testy. Reprodukcja robi się zielona, a trzy inne testy czerwone. Co zrobić: prompt fazy zielonej wymaga przebiegu całego zestawu, a
verify-tdd.shblokuje gałąź, dopóki zestaw nie przejdzie. - Trzydzieści testów przed pierwszą linijką kodu. To zwykły paraliż analityczny, tylko w przebraniu procesu. Co zrobić: trzy do pięciu testów podstawowego zachowania, implementacja, a potem przypadki brzegowe w osobnym przebiegu, w którym testy mają na początku padać.