Lerne einen Claude Code Prompt zur Testgenerierung, der hochwertige Tests erzeugt, indem er Grenzen, Invarianten und Fehlerarten statt nur Happy Paths anvisiert.

Auto-generierte Test-Suites sehen oft beeindruckend aus: Dutzende Tests, viel Setup-Code, und jeder Funktionsname taucht irgendwo auf. Viele dieser Tests sind aber nur „es funktioniert, wenn alles normal ist“-Überprüfungen. Sie bestehen leicht, fangen selten Bugs ein und verursachen trotzdem Aufwand beim Lesen und Warten.
Bei einem typischen Claude Code Test-Generierungs-Prompt neigt das Modell dazu, die Beispiel-Inputs zu spiegeln, die es sieht. Man erhält Variationen, die anders aussehen, aber dasselbe Verhalten abdecken. Das Ergebnis ist eine große Suite mit dünner Abdeckung an den relevanten Stellen.
High-Signal-Tests sind anders. Es ist die kleine Menge, die den Vorfall vom letzten Monat entdeckt hätte. Sie schlagen fehl, wenn sich das Verhalten in riskanter Weise ändert, und bleiben stabil bei harmlosen Refactorings. Ein High-Signal-Test kann zwanzig "gibt den erwarteten Wert zurück"-Checks ersetzen.
Niedrigwertige Happy-Path-Generierung hat meist einige deutliche Symptome:
Stell dir eine Funktion vor, die einen Rabattcode anwendet. Happy-Path-Tests bestätigen, dass "SAVE10" den Preis reduziert. Reale Bugs verstecken sich anderswo: 0- oder negative Preise, abgelaufene Codes, Rundungsgrenzen oder maximale Rabattgrenzen. Das sind die Fälle, die zu falschen Gesamtsummen, verärgerten Kund:innen und nächtlichen Rollbacks führen.
Das Ziel ist, von „mehr Tests" zu „besseren Tests" zu wechseln, indem man drei Ziele anvisiert: Grenzen, Fehlerarten und Invarianten.
Wenn du High-Signal-Unit-Tests willst, hör auf, nach „mehr Tests" zu fragen, und fordere drei konkrete Typen an. Das ist der Kern eines Claude Code Prompts, der nützliche Abdeckung liefert, anstatt einen Haufen „funktioniert bei normaler Eingabe"-Checks.
Grenzen sind die Ränder dessen, was der Code akzeptiert oder produziert. Viele reale Defekte sind Off-by-One-, Leerstates- oder Timeout-Probleme, die im Happy-Path nie auftreten.
Denk an Minimal- und Maximalwerte (0, 1, maximale Länge), leer vs. vorhanden ("", [], nil), Off-by-One (n-1, n, n+1) und Zeitlimits (nahe der Grenze).
Beispiel: Wenn eine API „bis zu 100 Items" akzeptiert, teste 100 und 101, nicht nur 3.
Fehlerarten sind die Wege, wie das System kaputtgehen kann: ungültige Eingaben, fehlende Abhängigkeiten, partielle Ergebnisse oder Fehler flussaufwärts. Gute Tests für Fehlerarten prüfen das Verhalten unter Stress, nicht nur die Ausgabe unter idealen Bedingungen.
Beispiel: Wenn ein Datenbankaufruf fehlschlägt, gibt die Funktion dann einen klaren Fehler zurück und vermeidet partielle Schreibvorgänge?
Invarianten sind Wahrheiten, die vor und nach einem Aufruf bestehen sollten. Sie verwandeln vage Korrektheit in präzise Assertions.
Beispiele:
Wenn du dich auf diese drei Ziele konzentrierst, bekommst du weniger Tests, aber jeder trägt mehr Signal.
Wenn du zu früh nach Tests fragst, erhältst du meist einen Haufen höflicher „funktioniert wie erwartet"-Checks. Eine einfache Lösung ist, zuerst einen winzigen Vertrag zu schreiben und dann Tests aus diesem Vertrag zu generieren. Das ist der schnellste Weg, um einen Claude Code Prompt in etwas zu verwandeln, das echte Bugs findet.
Ein nützlicher Vertrag ist kurz genug, um ihn auf einen Blick zu erfassen. Zielt auf 5 bis 10 Zeilen, die drei Fragen beantworten: Was kommt rein, was kommt raus, und was ändert sich sonst noch.
Schreibe den Vertrag in einfacher Sprache, nicht in Code, und nimm nur auf, was du testen kannst.
Sobald du das hast, scanne es danach, wo die Realität deine Annahmen brechen kann. Das werden Grenzfälle (Min/Max, Null, Überlauf, leere Strings, Duplikate) und Fehlerarten (Timeouts, Berechtigungsverweigerung, Unique-Constraint-Verstöße, beschädigte Eingaben).
Hier ein konkretes Beispiel für eine Funktion wie reserveInventory(itemId, qty):
Der Vertrag könnte sagen, qty muss eine positive Ganzzahl sein, die Funktion sollte atomar sein und niemals negativen Lagerbestand erzeugen. Das schlägt sofort High-Signal-Tests vor: qty = 0, qty = 1, qty größer als verfügbar, parallele Aufrufe und ein absichtlich ausgelöster Datenbankfehler in der Mitte.
Wenn du ein Chat-basiertes Tool wie Koder.ai verwendest, gilt derselbe Workflow: Schreibe zuerst den Vertrag im Chat, dann generiere Tests, die direkt Grenzen, Fehlerarten und die „darf niemals passieren"-Liste angreifen.
Verwende diesen Claude Code Test-Generierungs-Prompt, wenn du weniger Tests willst, aber jeder Test Gewicht hat. Die Schlüsselaktion ist, zuerst einen Testplan zu erzwingen und dann Testcode erst nach Freigabe des Plans zu generieren.
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.
Ein praktischer Trick ist, die Boundary-Matrix als kompakte Tabelle zu verlangen, damit Lücken offensichtlich werden:
| Dimension | Valid edge | Just outside | “Weird” value | Expected behavior |
|---|---|---|---|---|
| length | 0 | -1 | 10,000 | error vs clamp vs accept |
Wenn Claude 20 Tests vorschlägt, wehre dich. Fordere es auf, ähnliche Fälle zusammenzuführen und nur die zu behalten, die einen echten Bug fangen würden (Off-by-One, falscher Fehlertyp, stiller Datenverlust, gebrochene Invariante).
Beginne mit einem kleinen, konkreten Vertrag für das Verhalten, das du willst. Füge die Funktionssignatur, eine kurze Beschreibung der Ein- und Ausgaben und vorhandene Tests (auch wenn sie nur Happy-Path sind) ein. Das hält das Modell an dem, was der Code tatsächlich macht, statt an dem, was es vermutet.
Als Nächstes fordere eine Risikotabelle, bevor du Testcode verlangst. Bestehe auf drei Spalten: Grenzfälle (Eingabegrenzen), Fehlerarten (schlechte Eingaben, fehlende Daten, Timeouts) und Invarianten (Regeln, die immer gelten sollten). Füge pro Zeile einen Satz hinzu: „warum das brechen kann." Eine einfache Tabelle legt Lücken schneller offen als ein Haufen Testdateien.
Dann wähle die kleinste Menge von Tests, bei der jeder Test einen einzigartigen Bug-Fang-Zweck hat. Wenn zwei Tests aus demselben Grund fehlschlagen würden, behalte den stärkeren.
Eine praktische Auswahlregel:
Schließlich verlange für jeden Test eine kurze Erklärung: Welchen Bug würde er fangen, wenn er fehlschlägt. Wenn die Erklärung vage ist ("validates behavior"), ist der Test wahrscheinlich Low-Signal.
Eine Invariante ist eine Regel, die unabhängig von gültigen Eingaben wahr bleiben sollte. Beim invariant-basierten Testen schreibst du zuerst die Regel in einfacher Sprache und wandelst sie dann in eine Assertion um, die laut fehlschlägt.
Wähle 1 oder 2 Invarianten, die dich wirklich vor realen Bugs schützen. Gute Invarianten betreffen oft Sicherheit (kein Datenverlust), Konsistenz (gleiche Eingaben → gleiche Ausgabe) oder Grenzen (Kappen nie überschreiten).
Formuliere die Invariante kurz, und entscheide dann, welche Belege dein Test beobachten kann: Rückgabewerte, gespeicherte Daten, ausgesendete Events oder Aufrufe von Abhängigkeiten. Starke Assertions prüfen sowohl Ergebnis als auch Seiteneffekte, weil viele Bugs in „gibt OK zurück, hat aber das Falsche geschrieben" versteckt sind.
Beispiel: Angenommen, eine Funktion wendet einen Coupon auf eine Bestellung an:
Jetzt kodierst du das als konkrete Assertions:
expect(result.total).toBeGreaterThanOrEqual(0)
expect(db.getOrder(orderId).discountCents).toBe(originalDiscountCents)
Vermeide vage Assertions wie "returns expected result". Prüfe die spezifische Regel (nicht negativ) und den spezifischen Seiteneffekt (Rabatt nur einmal gespeichert).
Für jede Invariante füge eine kurze Notiz im Test hinzu, welches Datum sie verletzen würde. Das verhindert, dass der Test später zu einem Happy-Path-Check verkommt.
Ein einfaches Muster, das sich bewährt:
High-Signal-Tests sind häufig diejenigen, die bestätigen, dass dein Code sicher fehlschlägt. Wenn ein Modell nur Happy-Path-Tests schreibt, erfährst du wenig darüber, wie das Feature sich verhält, wenn Eingaben und Abhängigkeiten unordentlich werden.
Beginne damit, zu definieren, was „sicher" für dieses Feature bedeutet. Gibt es einen typisierten Fehler? Fällt es auf einen Default zurück? Versucht es genau einmal neu und stoppt dann? Schreibe dieses erwartete Verhalten in einem Satz und lass die Tests es beweisen.
Wenn du Claude Code um Tests für Fehlerarten bittest, halte das Ziel strikt: Decke die Wege ab, wie das System brechen kann, und behaupte die genaue gewünschte Antwort. Eine nützliche Zeile ist: „Weniger Tests mit stärkeren Assertions sind besser als viele flache Tests."
Fehlerkategorien, die tendenziell die besten Tests liefern:
Beispiel: Du hast einen Endpoint, der einen Nutzer erstellt und einen E-Mail-Service aufruft, um eine Willkommensnachricht zu senden. Ein Low-Value-Test prüft nur "returns 201". Ein High-Signal-Fehlertest prüft, dass wenn der E-Mail-Service time-outet, du entweder (a) den Nutzer trotzdem anlegst und 201 mit einem "email_pending"-Flag zurückgibst, oder (b) ein klares 503 zurückgibst und den Nutzer nicht anlegst. Wähle ein Verhalten und prüfe sowohl die Antwort als auch die Seiteneffekte.
Teste auch, was du nicht leakst. Wenn die Validierung fehlschlägt, stelle sicher, dass nichts in die Datenbank geschrieben wurde. Wenn eine Abhängigkeit eine beschädigte Payload zurückgibt, sorge dafür, dass du keine unbehandelten Exceptions oder rohe Stacktraces zurückgibst.
Niedrigwertige Test-Sets entstehen oft, wenn das Modell für Masse belohnt wird. Wenn dein Claude Code Prompt nach "20 Unit Tests" fragt, bekommst du oft kleine Variationen, die gründlich aussehen, aber nichts Neues fangen.
Häufige Fallen:
Beispiel: Stell dir eine create user-Funktion vor. Zehn Happy-Path-Tests könnten das E-Mail-String variieren und trotzdem das Wichtige verpassen: Duplikat-E-Mails abweisen, leeres Passwort behandeln und sicherstellen, dass zurückgegebene Nutzer-IDs eindeutig und stabil sind.
Schutzmaßnahmen, die bei Reviews helfen:
Stell dir ein Feature vor: Einen Coupon-Code im Checkout anwenden.
Contract (klein und testbar): Gegeben ein Warenkorb-Subtotal in Cent und ein optionaler Coupon, gib einen finalen Total-Betrag in Cent zurück. Regeln: Prozent-Coupons runden nach unten auf den nächsten Cent, fixe Coupons ziehen einen festen Betrag ab und Totals werden niemals negativ. Ein Coupon kann ungültig, abgelaufen oder bereits verwendet sein.
Frage nicht einfach: "Tests für applyCoupon()". Fordere Grenzfalltests, Fehlerarten und Invarianten, die an diesen Vertrag gebunden sind.
Wähle Eingaben, die Mathe oder Validierung typischerweise brechen: ein leerer Coupon-String, subtotal = 0, subtotal knapp unter und über einem Mindestbetrag, ein fixer Rabatt größer als das Subtotal und ein Prozentwert wie 33%, der Rundung erzeugt.
Angenommen, die Coupon-Suche kann fehlschlagen und Zustand kann falsch sein: Der Coupon-Service ist down, der Coupon ist abgelaufen oder wurde bereits von diesem Nutzer eingelöst. Der Test sollte zeigen, was dann passiert (Coupon abgelehnt mit klarem Fehler, Total unverändert).
Eine minimale, High-Signal-Testmenge (5 Tests) und was jeder aufdeckt:
Wenn diese Tests bestehen, hast du die üblichen Bruchstellen abgedeckt, ohne die Suite mit doppelten Happy-Path-Tests zu füllen.
Bevor du akzeptierst, was das Modell generiert hat, mach eine kurze Qualitätsprüfung. Das Ziel sind Tests, die dich jeweils vor einem spezifischen, wahrscheinlichen Bug schützen.
Benutze diese Checkliste als Tor:
Ein praktischer Trick nach der Generierung: Benenne Tests zu „should <Verhalten> when <Randbedingung>" und „should not <schlechtes Ergebnis> when <Fehler>". Wenn du sie nicht sauber umbenennen kannst, sind sie nicht fokussiert.
Wenn du mit Koder.ai arbeitest, passt diese Checkliste gut zu Snapshots und Rollback: generiere Tests, führ sie aus und rolle zurück, wenn die neue Menge Lärm hinzufügt ohne Coverage zu verbessern.
Behandle deinen Prompt als wiederverwendbares Werkzeug, nicht als One-Off. Speichere eine Blueprint-Vorlage (die das Erzwingen von Grenzen, Fehlerarten und Invarianten beinhaltet) und benutze sie für jede neue Funktion, jeden Endpoint oder Flow.
Eine einfache Gewohnheit, die Ergebnisse schnell verbessert: Fordere einen Satz pro Test, der erklärt, welchen Bug er fangen würde. Wenn der Satz generisch ist, ist der Test vermutlich Rauschen.
Führe eine lebende Liste von Domain-Invarianten für dein Produkt. Speichere sie nicht nur im Kopf. Ergänze sie, wann immer du einen echten Bug findest.
Ein leichter Workflow, den du wiederholen kannst:
Wenn du Apps per Chat baust, führe diesen Zyklus in Koder.ai (koder.ai) aus, damit Vertrag, Plan und generierte Tests an einem Ort bleiben. Wenn ein Refactor das Verhalten unerwartet ändert, machen Snapshots und Rollback es leichter, zu vergleichen und iterativ so lange zu arbeiten, bis deine High-Signal-Menge stabil bleibt.
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: