Prompt generujący testy Claude Code dla przypadków brzegowych
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.

Dlaczego generowanie testów tylko dla ścieżek „happy-path” marnuje czas
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:
- Wiele testów różni się tylko etykietami wejść, a nie tym, co może się zepsuć.
- Assercje są płytkie („nie null”, „status 200”) zamiast sprawdzać sens wyników.
- Przygotowanie stanu jest cięższe niż zachowanie, które jest testowane, więc ludzie przestają aktualizować testy.
- Pokrycie wygląda na wysokie, ale przypadki brzegowe są nietknięte.
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.
Te trzy cele: granice, tryby awarii, 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.
1) Granice (gdzie chowają się błędy)
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.
2) Tryby awarii (udowodnij, że system bezpiecznie upada)
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?
3) Inwarianty (zasady, które zawsze muszą się utrzymać)
Inwarianty to prawdy, które powinny być prawdziwe przed i po wywołaniu. Zamieniają niejasną poprawność w konkretne asercje.
Przykłady:
- „Saldo nigdy nie jest ujemne” po próbie wypłaty.
- „Id są unikalne” nawet przy szybkim tworzeniu elementów.
- „W przypadku błędu brak zmian stanu” (brak nowych wierszy, brak przestawionych flag).
Gdy skupisz się na tych trzech celach, dostaniesz mniej testów, ale każdy z nich niesie więcej sygnału.
Przygotowanie: wyodrębnij mały kontrakt przed pisaniem testów
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.
Szablon kontraktu (5–10 linii)
Napisz kontrakt prostym językiem, nie kodem, i zawrzyj tylko to, co da się przetestować.
- Wejścia: typy, dozwolone zakresy i co liczy się jako „puste” lub „brakujące”.
- Wyjście: wartość zwracana lub kształt błędu oraz co gwarantuje „sukces”.
- Efekty uboczne: zmiany stanu, wiersze w DB, wywołania sieciowe, pliki, logi.
- Założenia: rzeczy, które wywołujący często mylą (strefa czasowa, kodowanie, uwierzytelnienie, kolejność).
- „Nigdy nie powinno się zdarzyć”: awaria, utrata danych, podwójne obciążenie, częściowe zapisy.
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ć”.
Wzorzec promptu: plan wysokosygnałowych testów
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).
Krok po kroku: uruchom prompt i zamień wynik w testy
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:
- Zachowaj testy, które trafiają różne granice (min, max, empty, off-by-one).
- Zachowaj testy, które udowadniają bezpieczne zachowanie przy awarii (czytelny błąd, brak częściowego zapisu, brak awarii).
- Zachowaj testy, które asercjonują inwariant (kolejność, sumy, idempotencja, brak duplikatów).
- Usuń testy, które tylko powtarzają „działa dla normalnego wejścia.”
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.
Jak zamienić inwarianty w asercje
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).
Zamień inwariant w sprawdzenie, które da się udowodnić
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:
- Inwariant: końcowy total nigdy nie jest ujemny.
- Inwariant: zastosowanie tego samego kuponu dwa razy nie daje podwójnej zniżki.
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).
Dodaj notatkę z kontrprzykładem, by test pozostał ostry
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:
- Umieść inwariant w nazwie testu.
- Aserwuj inwariant na wyniku.
- Aserwuj kluczowy efekt uboczny (lub jego brak).
- Dodaj jeden komentarz opisujący przypadek łamiący (np. ogromna wartość kuponu lub podwójne zastosowanie).
Tryby awarii: pisz testy, które dowodzą bezpiecznego zachowania
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:
- Złe wejścia: nieprawidłowe formaty, brak wymaganych pól, wartości poza zakresem
- Błędy zależności: timeouty, 500, puste odpowiedzi, uszkodzone payloady
- Problemy z kolejnością: zdarzenia w złej kolejności, duplikaty, częściowe zapisy
- Równoczesność: wyścigi aktualizacji, sprawdzenia idempotencji
- Zachowanie przy odzyskiwaniu: kiedy zwracasz błąd vs fallback vs retry
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.
Typowe pułapki, które tworzą niskowartościowe testy
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:
- Testy podobne do siebie: ten sam „prawidłowy input” powtarzany z różnymi stringami lub liczbami.
- Testy odzwierciedlające kod: asercje prywatnych kroków lub wywołań helperów zamiast obserwowalnego zachowania.
- Mockowanie wszystkiego: zastąpienie bazy, zegara, sieci i konfiguracji na raz.
- Słabe asercje: tylko „brak błędu”, „nie null” albo „status 200”.
- Brudny stan współdzielony: zostawione zainicjowane dane, zmodyfikowane globale lub cache.
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:
- Wymagaj, by każdy test nazwał ryzyko, które pokrywa (granica, tryb awarii lub inwariant).
- Unikaj asercji zależnych wyłącznie od implementacji, chyba że wpływają na obserwowalne zachowanie.
- Trzymaj mocki do minimum i pozwól na kilka testów integracyjnych, jeśli to wykonalne.
- Wymagaj silnych asercji: dokładne wyjścia, zmiany stanu i typy/treści błędów.
- Dodaj reguły sprzątania, by testy nie zależały od kolejności.
Przykład: zamiana jednej funkcji w mały, mocny zestaw testów
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.
Granice, które wymuszają zachowanie na krawędziach
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.
Tryby awarii, które udowadniają bezpieczne zachowanie
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:
- Odrzuć pusty lub tylko-spacje kod: łapie błąd „akceptuje puste jako ważne” i złe przycinanie.
- Zaokrąglanie kuponu procentowego (subtotal 101, 33%): łapie błędy zaokrąglania i off-by-one centów.
- Stała zniżka większa niż subtotal (subtotal 500, discount 1000): dowodzi inwariantu, że total nigdy nie będzie ujemny.
- Granica minimalnego wydatku (subtotal 999 vs 1000): łapie złą logikę porównań (< vs <=).
- Awaria wyszukiwania kuponu lub timeout: dowodzi bezpiecznego fallbacku (brak zastosowanej zniżki) i stabilnej obsługi błędów.
Jeśli te przejdą, pokryłeś typowe punkty złamań bez zapełniania zestawu powtarzalnymi testami happy-path.
Szybka lista kontrolna dla wysokosygnałowych testów generowanych przez AI
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:
- Granice dla każdego pola wejściowego: dla każdego pola (stringi, ID, timestampy, flagi) uwzględnij przynajmniej jeden przypadek krawędziowy (puste vs tylko-spacje, maksymalna długość, zero vs ujemne, brak opcjonalnych pól, jeden poza limitem).
- Błędy zależności: uwzględnij przynajmniej jeden test, gdzie zależność źle się zachowuje (timeout DB, 500 API, przeterminowany token). Udowodnij bezpieczne zachowanie (czytelny błąd, brak częściowych zapisów).
- Inwarianty z silnymi asercjami: wybierz 1–3 reguły, które zawsze muszą się utrzymać i asercjonuj je bezpośrednio. Unikaj niejasnych asercji typu „response is ok”.
- Jeden unikalny błąd na test: przeczytaj tytuł każdego testu i zapytaj „Jaki dokładny błąd by to złapało?” Jeśli dwa testy odpowiadają na to samo pytanie, połącz je.
- Test usuwania: spróbuj usunąć test. Jeśli nic istotnego nie tracisz (żadna granica, żaden tryb awarii, żaden inwariant), test nie zasłużył na miejsce.
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.
Następne kroki: zamień to w powtarzalny workflow
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ć:
- Wyodrębnij mały kontrakt: wejścia, wyjścia, obsługa błędów i 3–5 inwariantów.
- Uruchom blueprint prompt i poproś o granice, tryby awarii, inwarianty oraz jednozdaniowe uzasadnienia.
- Zaimplementuj tylko 5–10 najlepszych testów, które pokrywają odrębne ryzyka.
- Refaktoryzuj, potem ponownie uruchom prompt, by zobaczyć nowe ryzyka.
- Odetnij duplikaty i zachowaj testy, które wykryłyby przeszłe incydenty.
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.
Często zadawane pytania
How many unit tests should I generate per function?
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.
What’s wrong with generating lots of happy-path tests?
Happy-path tests mostly prove that your example still works. They tend to miss the stuff that breaks in production.
High-signal tests target:
- Boundaries (0/1/max, empty/null, off-by-one)
- Failure modes (timeouts, invalid inputs, dependency errors)
- Invariants (rules that must always hold, like “no partial write on error”)
What should I write down before asking an AI to generate tests?
Start with a tiny contract you can read in one breath:
- Inputs: types, allowed ranges, what counts as empty/missing
- Outputs: success shape and error shape
- Side effects: what can be written/changed (DB, files, network)
- “Must never happen”: crash, silent data loss, double charge, partial writes
Then generate tests from that contract, not from examples alone.
Which boundary cases are usually worth testing?
Test these first:
- Min/max values (0, 1, max, max+1)
- Empty vs present ("", [], null/nil)
- Off-by-one (n-1, n, n+1)
- Formatting edges (whitespace-only strings, leading zeros)
- Time cutoffs (just before/after expiry)
Pick one or two per input dimension so each test covers a unique risk.
How do I write a good “failure mode” test instead of a shallow one?
A good failure-mode test proves two things:
- The function returns a clear, expected error (type/message/status).
- It fails safely:
- no partial state changes
- no leaked internal details
- no retries or side effects you didn’t intend
If there’s a database write involved, always check what happened in storage after the failure.
How do I turn an invariant into a test assertion?
Default approach: turn the invariant into an assertion on observable outcomes.
Examples:
- “Total never negative” →
expect(total).toBeGreaterThanOrEqual(0) - “On error, no state changes” → assert no new rows / no flags flipped
- “Idempotent” → call twice and assert the second call doesn’t change state
Prefer checking both return value and side effects, because many bugs hide in “returned OK but wrote the wrong thing.”
When is a happy-path test still worth writing?
It’s worth keeping a happy-path test when it protects an invariant or a critical integration.
Good reasons to keep one:
- It asserts a key invariant on normal input (e.g., rounding rules)
- It locks down an API contract that callers rely on
- It guards against a past incident regression
Otherwise, trade it for boundary/failure tests that catch more classes of bugs.
What should I ask the model to output before generating test code?
Push for PHASE 1: plan only first.
Require the model to provide:
- 6–10 proposed tests max
- For each: intent, setup, input, expected result, why it’s high-signal
- A small boundary matrix
- A failure-mode list
- 3–5 invariants and how to assert them
Only after you approve the plan should it generate code. This prevents “20 look-alike tests” output.
How do I avoid tests that are brittle because they mock too much?
Default: mock only the boundary you don’t own (DB/network/clock), and keep everything else real.
To avoid over-mocking:
- Don’t mock internal helpers just to mirror implementation
- Use a real in-memory version when feasible, or a small fake with clear behavior
- Mock the clock/randomness only when it affects the assertion
If a test breaks on refactor but behavior didn’t change, it’s often over-mocked or too implementation-coupled.
How can I quickly tell if an AI-generated test is low-value?
Use a simple deletion test:
- If you delete the test and lose no boundary, no failure mode, and no invariant, it didn’t earn its place.
Also scan for duplicates:
- If two tests would fail for the same bug, keep the one with the stronger assertion.
- If assertions are just “not null” or “status 200,” strengthen them or remove the test.