Poznaj prompt do generowania testów Claude Code, który tworzy wysokosygnałowe testy, celując w przypadki brzegowe, inwarianty i tryby awarii zamiast jedynie ścieżek happy-path.

Automatycznie generowane zestawy testów często wyglądają imponująco: dziesiątki testów, dużo kodu ustawiającego stan i każda nazwa funkcji gdzieś się pojawia. Jednak wiele z tych testów to jedynie sprawdzenia „działa, gdy wszystko jest normalne”. Łatwo przechodzą, rzadko wykrywają błędy i nadal kosztują czas na czytanie i utrzymanie.
Przy typowym promptcie do Claude Code model ma tendencję do naśladowania przykładów, które widzi. Dostajesz wariacje, które wydają się różne, lecz pokrywają to samo zachowanie. Efekt to duży zestaw z cienkim pokryciem tam, gdzie to naprawdę ma znaczenie.
Wysokosygnałowe testy są inne. To mały zestaw, który wykryłby incydent z zeszłego miesiąca. Padają, gdy zachowanie zmienia się w ryzykowny sposób, i pozostają stabilne przy bezpiecznych refaktoringach. Jeden wysokosygnałowy test może być wart dwudziestu „zwraca oczekiwaną wartość” sprawdzeń.
Generowanie niskowartościowych testów dla happy-path ma zwykle kilka charakterystycznych objawów:
Wyobraź sobie funkcję stosującą kod rabatowy. Testy happy-path potwierdzają, że „SAVE10” obniża cenę. Prawdziwe błędy chowają się gdzie indziej: ceny 0 lub ujemne, przeterminowane kody, problemy z zaokrąglaniem czy maksymalne limity zniżki. To właśnie te przypadki powodują złe sumy, niezadowolonych klientów i cofnięcia w środku nocy.
Celem jest przejście od „więcej testów” do „lepszych testów” poprzez celowanie w trzy obszary: granice (boundaries), tryby awarii (failure modes) i inwarianty.
Jeśli chcesz wysokosygnałowych testów jednostkowych, przestań prosić o „więcej testów” i zacznij prosić o trzy konkretne rodzaje. To sedno prompta dla Claude Code, który produkuje użyteczne pokrycie zamiast sterty sprawdzeń dla normalnego wejścia.
Granice to krawędzie tego, co kod akceptuje lub produkuje. Wiele rzeczywistych defektów to błędy o jeden (off-by-one), problemy ze stanem pustym lub timeouty, które nigdy nie pojawiają się w happy-path.
Myśl w kategoriach minimów i maksimów (0, 1, maksymalna długość), pustego kontra obecnego ("", [], nil), off-by-one (n-1, n, n+1) i limitów czasowych (blisko progu).
Przykład: jeśli API akceptuje „do 100 elementów”, przetestuj 100 i 101, a nie tylko 3.
Tryby awarii to sposoby, w jakie system może się zepsuć: złe dane wejściowe, brakujące zależności, częściowe wyniki czy błędy upstream. Dobre testy trybów awarii sprawdzają zachowanie pod obciążeniem, nie tylko wyjście w idealnych warunkach.
Przykład: gdy wywołanie bazy danych zawiedzie, czy funkcja zwraca czytelny błąd i unika zapisu częściowych danych?
Inwarianty to prawdy, które powinny być prawdziwe przed i po wywołaniu. Zamieniają niejasną poprawność w konkretne asercje.
Przykłady:
Gdy skupisz się na tych trzech celach, dostaniesz mniej testów, ale każdy z nich niesie więcej sygnału.
Jeśli poprosisz o testy zbyt wcześnie, zwykle dostaniesz stertę uprzejmych sprawdzeń „działa jak oczekiwano”. Proste rozwiązanie to najpierw napisać krótki kontrakt, a potem generować testy z tego kontraktu. To najszybszy sposób, by przemienić prompt Claude Code w coś, co znajduje prawdziwe błędy.
Przydatny kontrakt jest na tyle krótki, że można go przeczytać jednym tchem. Celuj w 5–10 linii odpowiadających na trzy pytania: co wchodzi, co wychodzi i co jeszcze się zmienia.
Napisz kontrakt prostym językiem, nie kodem, i zawrzyj tylko to, co da się przetestować.
Gdy to masz, przejrzyj kontrakt pod kątem miejsc, gdzie rzeczywistość może złamać założenia. To staje się przypadkami brzegowymi (min/max, zero, overflow, puste stringi, duplikaty) i trybami awarii (timeouty, odmowa uprawnień, naruszenie unikalności, uszkodzone dane wejściowe).
Oto konkretny przykład dla funkcji takiej jak reserveInventory(itemId, qty):
Kontrakt mógłby mówić, że qty musi być dodatnią liczbą całkowitą, funkcja powinna być atomowa i nigdy nie powinna tworzyć ujemnego stanu magazynowego. To od razu sugeruje wysokosygnałowe testy: qty = 0, qty = 1, qty większe niż dostępne, wywołania współbieżne i wymuszone błędy bazy danych w połowie operacji.
Jeśli używasz narzędzia vibe-coding takiego jak Koder.ai, ten sam workflow ma zastosowanie: najpierw napisz kontrakt na czacie, potem wygeneruj testy, które celują w granice, tryby awarii i listę „nigdy nie powinno się zdarzyć”.
Użyj tego promptu Claude Code, gdy chcesz mniej testów, ale każdy ma wagę. Kluczowy ruch to wymuszenie najpierw planu testów, a potem generowanie kodu testowego dopiero po zaakceptowaniu planu.
You are helping me write HIGH-SIGNAL unit tests.
Context
- Language/framework: <fill in>
- Function/module under test: <name + short description>
- Inputs: <types, ranges, constraints>
- Outputs: <types + meaning>
- Side effects/external calls: <db, network, clock, randomness>
Contract (keep it small)
1) Preconditions: <what must be true>
2) Postconditions: <what must be true after>
3) Error behavior: <how failures are surfaced>
Task
PHASE 1 (plan only, no code):
A) Propose 6-10 tests max. Do not include “happy path” unless it protects an invariant.
B) For each test, state: intent, setup, input, expected result, and WHY it is high-signal.
C) Invariants: list 3-5 invariants and how each will be asserted.
D) Boundary matrix: propose a small matrix of boundary values (min/max/empty/null/off-by-one/too-long/invalid enum).
E) Failure modes: list negative tests that prove safe behavior (no crash, no partial write, clear error).
Stop after PHASE 1 and ask for approval.
PHASE 2 (after approval):
Generate the actual test code with clear names and minimal mocks.
Praktyczny trik to wymaganie macierzy granic jako zwartej tabeli, by luki były oczywiste:
| Dimension | Valid edge | Just outside | “Weird” value | Expected behavior |
|---|---|---|---|---|
| length | 0 | -1 | 10,000 | error vs clamp vs accept |
Jeśli Claude proponuje 20 testów, naciskaj. Poproś o połączenie podobnych przypadków i zachowanie tylko tych, które złapałyby prawdziwy błąd (off-by-one, zły typ błędu, cicha utrata danych, złamany inwariant).
Zacznij od małego, konkretnego kontraktu dla zachowania, które chcesz. Wklej sygnaturę funkcji, krótkie opis wejść i wyjść oraz istniejące testy (nawet jeśli to tylko happy-path). To utrzymuje model przyklejonego do tego, co kod faktycznie robi, a nie do tego, co zgaduje.
Następnie poproś o tabelę ryzyka przed poproszeniem o kod testów. Wymagaj trzech kolumn: przypadki brzegowe (krawędzie prawidłowego wejścia), tryby awarii (złe wejście, brak danych, timeouty) i inwarianty (zasady, które zawsze muszą być prawdziwe). Dodaj jedno zdanie na wiersz: „dlaczego to może się zepsuć”. Prosta tabela ujawnia luki szybciej niż stos plików testowych.
Potem wybierz najmniejszy zestaw testów, gdzie każdy ma unikalny cel łapania błędu. Jeśli dwa testy padają z tego samego powodu, zachowaj silniejszy.
Praktyczne reguły wyboru:
Na koniec wymagaj krótkiego wyjaśnienia na test: jaki błąd by wykrył, gdyby padł. Jeśli wyjaśnienie jest niejasne („waliduje zachowanie”), test prawdopodobnie jest niskosygnałowy.
Inwariant to reguła, która powinna być prawdziwa niezależnie od prawidłowego wejścia. W testowaniu opartym na inwariantach najpierw napisz regułę prostym zdaniem, potem zamień ją w asercję, która może zawieść głośno.
Wybierz 1–2 inwarianty, które naprawdę cię chronią przed prawdziwymi błędami. Dobre inwarianty dotyczą bezpieczeństwa (brak utraty danych), spójności (te same wejścia → te same wyjścia) lub limitów (nigdy nie przekraczać kapy).
Napisz inwariant jako krótkie zdanie, potem zdecyduj, jakie dowody może zaobserwować test: wartości zwracane, zapisane dane, emisje zdarzeń lub wywołania zależności. Silne asercje sprawdzają i rezultat, i efekty uboczne, bo wiele błędów kryje się w „zwrócono OK, ale zapisano coś złego”.
Na przykład, gdy masz funkcję, która stosuje kupon do zamówienia:
Teraz zakoduj to jako konkretne asercje, które można zmierzyć:
expect(result.total).toBeGreaterThanOrEqual(0)
expect(db.getOrder(orderId).discountCents).toBe(originalDiscountCents)
Unikaj niejasnych asercji typu „zwraca oczekiwany wynik”. Aserwuj konkretną regułę (nieujemny total) i konkretny efekt uboczny (zniżka zapisana tylko raz).
Dla każdego inwariantu dodaj krótką notatkę w teście o danych, które by go złamały. To zapobiega zdradzeniu testu na rzecz happy-pathu.
Prosty wzorzec, który się sprawdza:
Wysokosygnałowe testy często potwierdzają, że kod bezpiecznie upada. Jeśli model pisze tylko testy happy-path, niewiele się dowiesz o tym, jak funkcja zachowuje się, gdy wejścia i zależności są brudne.
Zacznij od ustalenia, co znaczy „bezpiecznie” dla tej funkcji. Czy zwraca typowany błąd? Czy przechodzi na wartość domyślną? Czy robi jedno ponowienie, a potem przestaje? Zapisz to w jednym zdaniu i zmusz testy, by to udowodniły.
Gdy prosisz Claude Code o testy trybów awarii, trzymaj cel surowo: pokryj sposoby, w jakie system może się zepsuć, i asercjonuj dokładną reakcję, jaką chcesz. Przydatne sformułowanie: „Wolę mniej testów z silniejszymi asercjami niż wiele płytkich testów.”
Kategorie awarii, które dają najlepsze testy:
Przykład: endpoint tworzący użytkownika i wywołujący serwis email. Niskowartościowy test sprawdza „zwraca 201”. Wysokosygnałowy test awarii sprawdza, że jeśli serwis email timeoutuje, albo (a) dalej tworzysz użytkownika i zwracasz 201 z flagą „email_pending”, albo (b) zwracasz czytelne 503 i nie tworzysz użytkownika. Wybierz jedno zachowanie i sprawdź zarówno odpowiedź, jak i efekty uboczne.
Testuj też, czego nie wyciekasz. Jeśli walidacja zawiedzie, upewnij się, że nic nie zostało zapisane w DB. Jeśli zależność zwróciła uszkodzony payload, upewnij się, że nie rzucasz nieobsłużonego wyjątku lub nie zwracasz surowych stack trace'ów.
Niskowartościowe zestawy testów zwykle powstają, gdy model jest nagradzany za ilość. Jeśli prompt do Claude Code prosi o „20 unit tests”, często dostajesz drobne wariacje, które wyglądają na wszechstronne, ale nic nowego nie łapią.
Typowe pułapki:
Przykład: funkcja „create user”. Dziesięć testów happy-path może zmieniać stringi email i nadal przegapić ważne rzeczy: odrzucanie duplikatów, obsługę pustego hasła i zapewnienie, że zwracane ID są unikalne i stabilne.
Zasady pomagające w przeglądzie:
Wyobraź sobie funkcjonalność: zastosowanie kodu kuponu przy kasie.
Kontrakt (mały i testowalny): biorąc pod uwagę subtotal koszyka w centach i opcjonalny kupon, zwróć końcowy total w centach. Zasady: kupony procentowe zaokrąglają w dół do najbliższego centa, kupony stałe odejmują stałą kwotę, a total nigdy nie spadnie poniżej 0. Kupon może być nieprawidłowy, przeterminowany lub już użyty.
Nie pytaj „testy dla applyCoupon()”. Poproś o testy przypadków brzegowych, trybów awarii i inwariantów powiązanych z tym kontraktem.
Wybierz wejścia, które zwykle łamią arytmetykę lub walidację: pusty string kuponu, subtotal = 0, subtotal tuż poniżej i powyżej minimum, stała zniżka większa niż subtotal i procent jak 33%, który powoduje zaokrąglenie.
Załóż, że wyszukiwanie kuponu może zawieść i stan może być niepoprawny: serwis kuponów jest niedostępny, kupon jest przeterminowany lub już zrealizowany przez tego użytkownika. Test powinien udowodnić, co się dzieje dalej (kupon odrzucony z czytelnym błędem, total bez zmian).
Minimalny, wysokosygnałowy zestaw testów (5 testów) i co każdy łapie:
Jeśli te przejdą, pokryłeś typowe punkty złamań bez zapełniania zestawu powtarzalnymi testami happy-path.
Zanim zaakceptujesz wynik modelu, zrób szybki przegląd jakości. Cel to testy, które każda chronią cię przed konkretnym, prawdopodobnym błędem.
Użyj tej listy jako bramki:
Prosty trik po generacji: zmień nazwy testów na „should <zachowanie> when <warunek krawędziowy>” i „should not <zły rezultat> when <awaria>”. Jeśli nie możesz ich łatwo przemianować, nie są wystarczająco skupione.
Jeśli budujesz z Koder.ai, ta lista pasuje też do snapshotów i rollbacków: generuj testy, uruchamiaj je i cofnij, jeśli nowy zestaw dodaje szum bez poprawiania pokrycia.
Traktuj swój prompt jako wielokrotnego użytku szablon, nie jednorazowe żądanie. Zapisz jeden blueprint prompt (ten, który wymusza granice, tryby awarii i inwarianty) i używaj go do każdej nowej funkcji, endpointu lub przepływu UI.
Prosty nawyk, który szybko poprawia wyniki: wymagaj jednego zdania na test wyjaśniającego, jaki błąd by wykrył. Jeśli to zdanie jest ogólne, test prawdopodobnie jest szumem.
Trzymaj żywą listę domenowych inwariantów dla produktu. Nie przechowuj jej w głowie. Dodawaj za każdym razem, gdy znajdziesz prawdziwy błąd.
Lekki workflow, który możesz powtarzać:
Jeśli tworzysz aplikacje przez chat, wykonaj ten cykl wewnątrz Koder.ai (koder.ai), żeby kontrakt, plan i wygenerowane testy były w jednym miejscu. Gdy refactor nieoczekiwanie zmienia zachowanie, snapshoty i rollback ułatwiają porównanie i iterację, aż twój wysokosygnałowy zestaw pozostanie stabilny.
Default: aim for a small set that would catch a real bug.
A quick cap that works well is 6–10 tests per unit (function/module). If you need more, it usually means your unit is doing too much or your contract is unclear.
Happy-path tests mostly prove that your example still works. They tend to miss the stuff that breaks in production.
High-signal tests target:
Start with a tiny contract you can read in one breath:
Then generate tests from that contract, not from examples alone.
Test these first:
Pick one or two per input dimension so each test covers a unique risk.
A good failure-mode test proves two things:
If there’s a database write involved, always check what happened in storage after the failure.
Default approach: turn the invariant into an assertion on observable outcomes.
Examples:
expect(total).toBeGreaterThanOrEqual(0)Prefer checking both and , because many bugs hide in “returned OK but wrote the wrong thing.”
It’s worth keeping a happy-path test when it protects an invariant or a critical integration.
Good reasons to keep one:
Otherwise, trade it for boundary/failure tests that catch more classes of bugs.
Push for PHASE 1: plan only first.
Require the model to provide:
Only after you approve the plan should it generate code. This prevents “20 look-alike tests” output.
Default: mock only the boundary you don’t own (DB/network/clock), and keep everything else real.
To avoid over-mocking:
If a test breaks on refactor but behavior didn’t change, it’s often over-mocked or too implementation-coupled.
Use a simple deletion test:
Also scan for duplicates: