Strategie i generowanie testów jednostkowych z agentami AI
Testy jednostkowe wygenerowane przez agenta AI łapią prawdziwe błędy, gdy człowiek zatwierdza listę zachowań, zanim powstanie jakikolwiek test, mocki zostają na granicach serwisów (baza danych, zewnętrzne API), a każdy test ma dowód, że potrafi nie przejść. Narzędzie mutacyjne, takie jak Stryker, pokazuje potem, których celowo wstrzykniętych błędów nowe testy wciąż nie widzą.
Ta strona jest dla programistów, którzy proszą agenta o testy jednostkowe, i dla tech leadów, którzy decydują, co w zespole znaczy „przetestowane”. Prosisz o „testy jednostkowe dla UserService” i dostajesz 40 zielonych testów przy 95% pokrycia linii. Tydzień później użytkownicy z plusem w adresie e-mail nie mogą się zarejestrować. Te 40 testów sprawdzało jedną ścieżkę szczęśliwą w 40 wariantach, a nikt nie czytał ich na tyle uważnie, żeby to zauważyć.
Co wyniesiesz z testów jednostkowych pisanych przez agenta
Dział zatytułowany „Co wyniesiesz z testów jednostkowych pisanych przez agenta”- Pięciokrokowy przepływ, który zamienia moduł w listę zachowań, testy, kontrole negatywne i przebieg mutacyjny.
- Pięć promptów do skopiowania: lista zachowań, generowanie testów, mockowanie na granicach, ocalałe mutanty i aktualizacja testów po refaktoryzacji.
- Regułę mockowania, którą wpiszesz do
AGENTS.mdalboCLAUDE.md, żeby agent przestał mockować testowany kod. - Kontrole, które pozwalają przyjąć plik testów bez czytania każdej asercji, oraz to, kto je zatwierdza.
- Sposoby naprawy sześciu typowych awarii testów jednostkowych pisanych przez agenta.
Dlaczego testy pisane przez agenta przechodzą, a nie łapią błędów?
Dział zatytułowany „Dlaczego testy pisane przez agenta przechodzą, a nie łapią błędów?”Agent, który pisze testy do istniejącego kodu, najpierw czyta implementację. Potem pisze testy opisujące to, co kod robi, łącznie z jego błędami, i wybiera dane wejściowe, które kod już obsługuje. Wynikają z tego cztery wzorce:
- Warianty ścieżki szczęśliwej. Wiele testów przechodzi przez tę samą gałąź z innymi danymi, więc pokrycie rośnie, a nic nowego nie jest sprawdzane.
- Mockowanie samej jednostki. Agent mockuje wewnętrzne funkcje pomocnicze, więc test weryfikuje mocki.
- Asercje na wywołaniach, nie na wynikach.
expect(repo.save).toHaveBeenCalled()przechodzi niezależnie od tego, co zapisano. - Edycja do zielonego. Gdy test nie przechodzi, agent zmienia asercję albo kod produkcyjny, aż test przejdzie.
Każdy z tych wzorców przetrwa zielony przebieg i raport pokrycia. Poniższy przepływ zamyka każdy z nich kontrolą, którą uruchamiasz, a nie linijką, którą musisz przeczytać. Miary, na których to się opiera (wynik mutacyjny, pokrycie zachowań, pochodzenie testów i niestabilność), są zdefiniowane na stronie jak silna jest twoja wyrocznia.
Generuj testy jednostkowe z zatwierdzonej listy zachowań
Dział zatytułowany „Generuj testy jednostkowe z zatwierdzonej listy zachowań”Kolejność ma znaczenie: najpierw zatwierdzasz, co jest testowane, a dopiero potem agent pisze, jak. W nowym kodzie zrób to przed implementacją, jak w TDD z agentami AI. W kodzie legacy, którego zachowania nikt nie potrafi opisać, najpierw utrwal to, co robi dziś, testami charakteryzacyjnymi.
-
Poproś o listę zachowań, nie o testy. Poproś agenta o numerowaną listę zachowań jednej publicznej metody, pogrupowaną na ścieżkę szczęśliwą, wartości graniczne, niepoprawne wejście i awarie zależności. Każda linia podaje wejście i oczekiwany wynik. Jeśli istnieje user story, wyprowadź listę z jej wykonywalnych kryteriów akceptacji.
-
Zatwierdź i popraw listę. To jedyny artefakt, który czytasz linijka po linijce. Jest krótki i to w nim wychodzą na jaw domysły agenta co do zamierzonego zachowania. Oznacz jako
BUGkażdą linię, w której obecne zachowanie kodu jest błędne, żeby agent napisał dla niej test, który nie przechodzi, zamiast utrwalać błąd. -
Wygeneruj jeden test na każdą zatwierdzoną linię. Nazwa testu powtarza linię, test przechodzi przez publiczne API, a mocki są tylko na granicach (patrz następna sekcja). Agent uruchamia plik i poprawia nieprzechodzące testy, nigdy kod produkcyjny, chyba że linia ma oznaczenie
BUG. -
Udowodnij, że każdy test potrafi nie przejść. Dla każdego testu agent psuje zachowanie, które ten test sprawdza (odwraca porównanie, dodaje wczesny
return), uruchamia test, potwierdza, że jest czerwony, i cofa zmianę. To jest kontrola negatywna. Test, który zostaje zielony przy zepsutej implementacji, jest usuwany albo pisany od nowa. -
Przetestuj plik mutacyjnie i zabij ocalałe mutanty. Uruchom Strykera na jednym pliku produkcyjnym, oddaj agentowi ocalałe mutanty i powtarzaj, aż każdy zostanie zabity albo sklasyfikowany jako równoważny. Zacommituj testy, a listę zachowań wklej do opisu pull requesta.
Zanim uruchomisz ten prompt, zacommituj order.service.ts albo odłóż go do stasha. Jeśli agent zignoruje polecenie i cofnie zmianę przez git checkout --, stracisz niezacommitowaną implementację, a nie tylko jednolinijkowe zepsucie. Przy pracy test-first implementacja często nie jest jeszcze zacommitowana.
Gdzie umieścić mocki w teście jednostkowym?
Dział zatytułowany „Gdzie umieścić mocki w teście jednostkowym?”Mockuj tam, gdzie twój kod rozmawia z czymś, czego nie jest właścicielem, a wszędzie indziej uruchamiaj prawdziwy kod. Nadmiar mocków sprawia, że test niczego nie dowodzi; brak mocków robi z testu jednostkowego test wolny i niestabilny. Wpisz tę regułę do pliku instrukcji, żeby każda sesja zaczynała się od niej.
| Zależność | W teście jednostkowym | Dlaczego |
|---|---|---|
| Repozytorium bazy danych, klient HTTP, kolejka komunikatów, system plików | Mock albo fake w pamięci | Wolne lub niedeterministyczne, a do tego należą do kogoś innego |
| Zegar, liczby losowe, UUID | Kontroluj je (fałszywe timery, generator z ziarnem, wstrzykiwana funkcja ID) | Inaczej testy przechodzą albo nie w zależności od pory dnia |
| Twoje funkcje walidacji, mapowania i obliczeń | Prawdziwe | To jest testowane zachowanie |
| Biblioteki kryptograficzne i haszujące | Prawdziwe, chyba że są celowo wolne (koszt bcrypt) | Zmockowany hash ukrywa błędy kodowania |
| Logowanie i zapisy audytowe | Prawdziwe albo szpiegowane; gdy to wymaganie, sprawdź, co zostało zapisane | Logi audytowe to zachowanie, logi debugowe nie |
Dopisz do AGENTS.md / CLAUDE.md:
## Testy jednostkowe- Mockuj tylko granice: bazę danych, HTTP, kolejki, system plików, zegar, losowość/UUID.- Nigdy nie mockuj kodu wewnątrz testowanego modułu; testuj go przez publiczne API.- Sprawdzaj zwracane wartości i zapisane efekty uboczne, nie tylko wywołania mocków.- Nigdy nie pisz gołego `toThrow()`; sprawdzaj typ błędu.- Używaj fałszywych timerów i danych z ziarnem; bez prawdziwego czasu i losowości bez ziarna.- Nigdy nie zmieniaj kodu produkcyjnego, żeby test przeszedł; zgłoś niepowodzenie.Dane testowe należą do fabryk z realistycznymi wartościami domyślnymi i jawnymi nadpisaniami. Wzorzec fabryki, seedy i syntetyczne dane bez danych osobowych opisuje strona strategie danych testowych i fixtures.
Jak sprawić, żeby agent przetestował ścieżki błędów i przypadki brzegowe?
Dział zatytułowany „Jak sprawić, żeby agent przetestował ścieżki błędów i przypadki brzegowe?”Nazwij je. Agent pozostawiony sam sobie wybiera wejścia, które kod już obsługuje, więc prompt musi wymienić klasy danych wejściowych. Dla każdej publicznej metody poproś o testy na granicach każdego zakresu liczbowego, dla pustych i jednoelementowych kolekcji, dla Unicode i białych znaków w napisach, dla zaokrągleń i dla każdego sposobu, w jaki może zawieść każda zależność. Regułę, która obowiązuje dla wszystkich wejść („suma nigdy nie jest ujemna”), garść przykładów potwierdza słabo; zapisz ją jako właściwość, jak w testach opartych na właściwościach dla kodu pisanego przez agentów.
Testy błędów mają jeden dodatkowy wymóg: sprawdzaj typ błędu i efekty uboczne, a nie tylko to, że coś zostało rzucone. Test z toThrow() bez argumentu przejdzie, gdy kod rzuci TypeError z powodu literówki.
Jak ustabilizować czas, losowość i kod asynchroniczny w testach
Dział zatytułowany „Jak ustabilizować czas, losowość i kod asynchroniczny w testach”Testy jednostkowe pisane przez agenta często używają prawdziwego zegara, losowych danych bez ziarna albo czekania przez setTimeout, a potem losowo nie przechodzą w CI. Wymagaj vi.useFakeTimers() i vi.setSystemTime() dla wszystkiego, co zależy od daty, stałego ziarna dla każdego generatora danych i oczekiwania na obietnice (await) zamiast uśpień.
Nie przeceniaj testów współbieżności. Test jednostkowy, który odpala dwa wywołania przez Promise.all, przeplata je tylko w punktach await wewnątrz jednego procesu. Złapie błąd typu „sprawdź, potem działaj” w twoim kodzie asynchronicznym, ale nie udowodni, że dwa workery albo dwie transakcje bazodanowe się nie ścigają. Taki dowód wymaga prawdziwej bazy, czyli testów integracyjnych.
Zabij ocalałe mutanty w pliku, który właśnie przetestowałeś
Dział zatytułowany „Zabij ocalałe mutanty w pliku, który właśnie przetestowałeś”Testowanie mutacyjne wprowadza drobne zmiany w kodzie produkcyjnym (> staje się >=, znika wywołanie) i ponownie uruchamia testy. Mutant, którego żaden test nie zauważył, przeżył, a każdy ocalały to błąd, którego twoje testy by nie złapały. Uruchamiaj to na jednym pliku, który właśnie przetestowałeś, żeby pętla trwała minuty, a nie godziny.
# Terminal, z katalogu głównego repozytorium (Stryker 10.0.0, sprawdzone 2026-09-26)npm install --save-dev @stryker-mutator/core @stryker-mutator/vitest-runnerZapisz to jako stryker.config.json:
{ "testRunner": "vitest", "reporters": ["clear-text", "html", "json"], "incremental": true, "thresholds": { "high": 80, "low": 60, "break": 60 }}# Mutuj tylko testowany plik; reporter json zapisuje reports/mutation/mutation.jsonnpx stryker run --mutate src/services/order.service.tsDla Jesta zainstaluj @stryker-mutator/jest-runner i ustaw "testRunner": "jest". thresholds.break sprawia, że Stryker kończy się kodem wyjścia 1 poniżej tego wyniku; 60 to wartość startowa, nie benchmark. Wybór progu dla zespołu i podpięcie bramki dla każdego pull requesta w CI opisuje strona jak silna jest twoja wyrocznia.
Listę EQUIVALENT przejrzyj sam. Jest krótka, a właśnie w niej agent może ukryć prawdziwą lukę za pewnie brzmiącym zdaniem.
Jak to uruchomić w Claude Code, Codeksie i Cursorze?
Dział zatytułowany „Jak to uruchomić w Claude Code, Codeksie i Cursorze?”Przepływ, prompty i polecenia Strykera są takie same we wszystkich trzech narzędziach. Różni się to, jak utrzymać krok z listą zachowań w trybie tylko do odczytu i jak zainstalować skill, który klasyfikuje ocalałe mutanty. Polecenia sprawdzono na Claude Code 2.1.283, Codex CLI 0.157.1 i marketplace Trail of Bits w dniu 2026-09-26.
Trail of Bits publikuje wtyczkę mutation-testing (1.9.2). Jej skill czyta kod źródłowy i testy stojące za każdym ocalałym mutantem, oddziela mutanty równoważne od luk w testach i zapisuje rekomendacje do mutation-testing-report.md, nie wdrażając ich. Kampanie konfiguruje tylko dla mewt i muton, ale README mówi, że krok analizy „also accepts results from other mutation testing tools”, więc możesz podać mu mutation.json ze Strykera.
Zapisz dwa pierwsze prompty jako prompts/behaviour-list.md i prompts/generate-tests.md; poniższe polecenia czytają je stamtąd.
Lista zachowań, tylko do odczytu, w trybie headless (bez sesji interaktywnej):
# Terminal, z katalogu głównego repozytoriumclaude -p "$(cat prompts/behaviour-list.md)" \ --tools "Read,Grep,Glob" \ --max-budget-usd 2 > order-behaviours.md--tools ogranicza sesję do wymienionych wbudowanych narzędzi, więc Edit, Write i Bash w ogóle nie są dostępne. Samo --allowedTools tylko z góry zatwierdza narzędzia i zostawia pozostałe, więc liberalne ustawienia projektu albo tryb uprawnień nadal mogłyby pozwolić na edycję plików (sprawdzone na Claude Code 2.1.283).
Testy i kontrole negatywne: drugi prompt uruchom w interaktywnej sesji claude, żeby widzieć każde zepsucie i cofnięcie. Trzymaj zatwierdzoną listę zachowań w sesji, a nie tylko w pliku, który agent może edytować.
Skill do klasyfikacji ocalałych mutantów:
claude plugin marketplace add trailofbits/skillsclaude plugin install mutation-testing@trailofbitsKonfigurację specyficzną dla narzędzia, w tym komendę wielokrotnego użytku do generowania testów, opisuje strona testowanie z Claude Code.
Lista zachowań, tylko do odczytu, w trybie headless (bez sesji interaktywnej):
# Terminal, z katalogu głównego repozytorium (Codex CLI 0.157.1)codex exec -c default_permissions=":read-only" -o order-behaviours.md "$(cat prompts/behaviour-list.md)":read-only to wbudowany profil uprawnień, czyli mechanizm zalecany przez OpenAI. Profile są w wersji beta, nie mają osobnej flagi i nie łączą się ze starszą flagą --sandbox, więc nie podawaj obu w jednym poleceniu.
Testy i kontrole negatywne: drugi prompt zapisuje pliki i uruchamia Vitest, więc potrzebuje zapisywalnego obszaru roboczego. --approve-for-me kieruje prośby o zgodę do automatycznego przeglądu w sandboksie workspace-write:
codex exec --approve-for-me "$(cat prompts/generate-tests.md)"Skill do klasyfikacji ocalałych mutantów:
codex plugin marketplace add trailofbits/skillscodex plugin add mutation-testing@trailofbitsWięcej o testowaniu w Codeksie znajdziesz na stronie testowanie z Codexem.
Lista zachowań: wklej pierwszy prompt do agenta w Plan Mode, żeby zdał raport, zanim cokolwiek zapisze. Wskaż plik przez @src/services/order.service.ts.
Testy i kontrole negatywne: drugi prompt uruchom w nowym czacie, który zawiera zatwierdzoną listę, żeby agent pracował z listy, a nie ze swojego wcześniejszego odczytu implementacji. Zanim zaakceptujesz, sprawdź, że git status pokazuje tylko pliki testów.
Skill do klasyfikacji ocalałych mutantów: zainstaluj go przez CLI skills, które instaluje skille dla wielu agentów:
npx skills add trailofbits/skills --skill mutation-testing --agent cursorWzorce specyficzne dla Cursora opisuje strona wzorce testowania w Cursorze.
Aktualizuj testy po refaktoryzacji, nie zmieniając tego, co dowodzą
Dział zatytułowany „Aktualizuj testy po refaktoryzacji, nie zmieniając tego, co dowodzą”Gdy zmienia się interfejs, agent powinien zmienić sposób sprawdzania wyników, a to, co jest sprawdzane, zostawić bez zmian. Powiedz mu to wprost i poproś o dowód, że scenariusze się nie zmieniły.
Skąd wiesz, że testy jednostkowe są dobre, jeśli ich nie czytasz?
Dział zatytułowany „Skąd wiesz, że testy jednostkowe są dobre, jeśli ich nie czytasz?”Czytaj listę zachowań; resztę niech zweryfikują automatyczne kontrole. Plik testów jest gotowy do scalenia, gdy spełnione są wszystkie warunki:
| Kontrola | Skąd się bierze | Co łapie |
|---|---|---|
| Każde zatwierdzone zachowanie ma dokładnie jeden nazwany test | Porównanie listy z nazwami it( | Brakujące scenariusze, wymyślone scenariusze |
| Każdy test ma kontrolę negatywną, która była czerwona | Tabela z promptu generującego testy | Testy, które nie potrafią nie przejść |
Wynik mutacyjny zmienionego pliku przekracza próg break zespołu, ocalałe mutanty sklasyfikowane | npx stryker run --mutate <plik> | Słabe asercje, brakujące wartości graniczne |
Zmieniły się tylko pliki testów, chyba że linię oznaczono BUG | git diff --name-only | Kod produkcyjny zmieniony, żeby testy przeszły |
| Brak testów zawężonych lub pominiętych | Vitest w CI domyślnie odrzuca .only (--allowOnly domyślnie ma wartość !process.env.CI; Vitest 5.0.3, sprawdzone 2026-09-26); przeszukaj diff pod kątem .skip, it.todo i it.fails; każde it.fails musi odpowiadać linii oznaczonej BUG na zatwierdzonej liście | Testy wyłączone, żeby było zielono |
| Testy przechodzą w losowej kolejności | npx vitest run --sequence.shuffle | Współdzielony stan mutowalny między testami |
Pull request podpisuje programista, który zatwierdził listę zachowań, a dowody z testów trafiają do pakietu dowodów. Tech lead odpowiada za próg break i za regułę, kto może zmienić zatwierdzony test. Gdy testy zaczynają oceniać pracę agenta, zablokuj je, żeby agent implementujący nie mógł ich edytować, jak opisuje ochrona wyroczni.
Co się psuje, gdy agenci piszą testy jednostkowe?
Dział zatytułowany „Co się psuje, gdy agenci piszą testy jednostkowe?”Wszystkie testy wyglądają tak samo. Prompt wskazał metodę, ale nie zachowania. Usuń plik, uruchom prompt z listą zachowań i generuj wyłącznie z zatwierdzonej listy.
Testy sprawdzają tylko, czy mocki zostały wywołane. Tabela kontroli negatywnych pokazuje zielone wyniki przy zepsutym kodzie. Poproś agenta, żeby zastąpił każdą asercję na samym wywołaniu asercją na zwróconej wartości albo na zapisanym argumencie (toHaveBeenCalledWith z dokładnym obiektem), a potem ponownie uruchom kontrole negatywne.
Agent poprawił kod albo osłabił asercję, żeby było zielono. git diff --name-only pokazuje plik produkcyjny albo zmieniła się oczekiwana wartość. Cofnij zmianę w kodzie produkcyjnym, przywróć pierwotną asercję i potraktuj nieprzechodzący test jako znalezisko: albo lista zachowań była błędna, albo kod ma błąd. Rozstrzygnij, które z nich, a jeśli winny jest kod, oznacz linię jako BUG.
Testy utrwalają istniejący błąd. Testy pisane na podstawie implementacji zapisują to, co ona robi. Obroną jest przegląd listy zachowań; jeśli błąd wyjdzie później, najpierw popraw oczekiwaną wartość w teście, zobacz, że test jest czerwony, a dopiero potem popraw kod. Po poprawieniu kodu usuń oznaczenie it.fails, bo inaczej przechodzący już test stanie się czerwony.
Testy przechodzą lokalnie, a w CI nie. Uruchom je z --sequence.shuffle, żeby ujawnić zależność od kolejności, resetuj mocki przed każdym testem (vi.resetAllMocks() w beforeEach albo mockReset: true w konfiguracji Vitest) i zastąp prawdziwy zegar oraz dane losowe bez ziarna wartościami kontrolowanymi.
Pętla mutacyjna nigdy się nie domyka. Pozostałe mutanty są równoważne albo siedzą w logowaniu i kodzie defensywnym. Zakończ, gdy każdy ocalały mutant jest sklasyfikowany, zapisz równoważne w pull requeście, a fragment pliku wyłączaj z mutacji tylko z komentarzem, który wyjaśnia dlaczego.