KI-generierte Tests, die Bugs verbergen: Was wir bei Audits von Vibe-Coded-Codebases finden

#KI-generierte Tests
Sandor Farkas - Founder & Lead Developer at Wolf-Tech

Sandor Farkas

Gründer & Lead Developer

Experte für Softwareentwicklung und Legacy-Code-Optimierung

Eine grune Test-Suite soll etwas bedeuten. Wenn du eine Vibe-Coded-Codebase zum ersten Mal offnest und vierhundert erfolgreiche Tests siehst, ist dein erster Gedanke Erleichterung. Schaust du genauer hin, verfliegt diese Erleichterung. KI-generierte Tests haben eine typische Fehlersignatur - sie sind syntaktisch plausibel, strukturell vertraut und funktional leer. Sie bestehen, weil das Tooling einen Test-Run protokolliert, nicht weil etwas Echtes verifiziert wurde.

Wir sehen dieses Muster in fast jeder Codebase, die wir bei Projekten auditieren, die mit einem Sprachmodell am Steuer begonnen haben. Dieser Beitrag katalogisiert, was wir tatsachlich finden - keine theoretischen Bedenken, sondern wiederkehrende Muster aus echten Codebases - und erklart, warum diese Fehler schwerer zu erkennen sind als klassische Test-Anti-Patterns.

Was KI-generierte Tests von gewohnlich schlechten Tests unterscheidet

Bevor wir die Fehlermuster katalogisieren, lohnt es sich zu benennen, was dieses Problem ungewohnlich macht. Schlechte Tests, die von Menschen geschrieben wurden, sind meist auf lesbare Weise schlecht: eine fehlende Assertion, ein Test, der nie an CI angebunden wurde, ein Integrationstest, den jemand deaktiviert hat, weil er flaky wurde. Schlechte Tests von Sprachmodellen sind auf unsichtbare Weise schlecht, weil das Ziel des Modells darin besteht, Output zu erzeugen, der fur ein anderes Modell, einen Linter und einen Reviewer, der zugig vorangeht, korrekt aussieht.

Ein menschlicher Entwickler, der eine echte Assertion weglasst, tut das meist wegen Zeitdruck oder Unachtsamkeit. Ein Sprachmodell lasst echte Assertions weg, weil nichts zu behaupten der Weg des geringsten Widerstands ist, wenn das Modell den erwarteten Output der Funktion, die es testet, nicht kennt. Der Test sieht trotzdem wie ein Test aus. Er importiert die Klasse, instanziiert das Objekt, ruft die Methode auf und schreibt am Ende eine expect- oder assert-Zeile. Die Assertion behauptet nur etwas, das immer wahr ist.

Das ist der Kern des Problems: KI-generierte Test-Suites optimieren fur oberflachliche Plausibilitat statt echter Verifizierung. Du bekommst Tests, die eine PR-Review auf den ersten Blick bestehen wurden, aber keinen Regressionsschutz bieten.

Fehlermuster 1: Assertions gegen gemockte Ruckgabewerte

Das haufigste Muster, das wir finden, ist ein Test, der eine Abhangigkeit mockt, den Mock so konfiguriert, dass er einen bestimmten Wert zuruckgibt, den Code unter Test aufruft und dann assertiert, dass der Ruckgabewert mit dem ubereinstimmt, was der Mock zuruckgeben sollte. Die Assertion ist zirkuler - sie kann nur fehlschlagen, wenn der Mock selbst fehlerhaft ist, was nicht moglich ist, da der Mock vom Test kontrolliert wird.

Hier ist ein vereinfachtes PHP-Beispiel in dem Stil, den wir antreffen:

public function testCalculateOrderTotal(): void
{
    $pricingService = $this->createMock(PricingService::class);
    $pricingService->method('getUnitPrice')->willReturn(49.99);

    $calculator = new OrderCalculator($pricingService);
    $result = $calculator->calculateTotal(quantity: 2);

    $this->assertEquals(99.98, $result);
}

Dieser Test sieht vernunftig aus. Er mockt einen externen Service, was gute Praxis ist. Aber uberlege, was er tatsachlich verifiziert: Wenn jemand OrderCalculator::calculateTotal andert, sodass es mit drei statt zwei multipliziert oder eine Pauschale addiert, fangt dieser Test nichts - solange die Methode getUnitPrice aufruft und mit irgendetwas multipliziert. Der Assertion-Wert wurde vom Modell hartkodiert, um mit dem erwarteten Output der korrekten Implementierung ubereinzustimmen, nicht aus der Logik dessen abgeleitet, was der Kalkulator tun soll.

Der echte Test wurde Daten aufsetzen, den Kalkulator mit mehreren Mengenwerten durchlaufen und die mathematische Beziehung assertieren - nicht ein einziges hartkodiertes Produkt aus zwei hartodierten Zahlen.

Fehlermuster 2: Tests, die den relevanten Code-Pfad nie ausfuhren

Sprachmodelle schreiben Tests fur die Funktion, die sie testen sollen. Sie schreiben nicht unbedingt Tests, die die interessanten Pfade durch diese Funktion ausfuhren. Was wir haufig sehen, ist ein Test, der den Happy Path einmal aufruft, eine bestandene Assertion produziert und jeden bedingten Branch untestet lasst. Coverage-Tools berichten die Datei als abgedeckt. Die Branches, die Bugs enthalten - typischerweise die Error-Handling-Pfade, die Edge Cases und die Szenarien, in denen ein externer Aufruf fehlschlagt - werden nie ausgefuhrt.

In einem typischen Audit kann ein Projekt 78% Line Coverage zeigen, die sich auf unter 30% Branch Coverage in derselben Codebase reduziert. Der Unterschied besteht fast vollstandig aus KI-generierten Tests, die die ersten Zeilen jeder Funktion abdecken, aber zuruckkehren, bevor sie die bedingte Logik erreichen.

Das ist in der Produktion bedeutsam, weil die nicht getesteten Branches uberproportional die Fehlerpfade sind. Ein Bug im Happy Path taucht normalerweise wahrend der Entwicklung auf. Ein Bug im Error-Handling-Pfad taucht auf, wenn ein echter Nutzer sonntags um 2 Uhr nachts auf einen Edge Case trifft.

Fehlermuster 3: Implementation Mirroring

Ein subtileres Fehlermuster ist das, was wir Implementation Mirroring nennen: Tests, die die Implementierungslogik reproduzieren statt das erwartete Verhalten von aussen zu spezifizieren. Der Test und der Code unter Test sind logisch identisch, sodass sie alle dieselben Bugs teilen.

In einem JavaScript-Beispiel:

it('should format currency', () => {
  const amount = 1234.5;
  const result = formatCurrency(amount);
  expect(result).toBe(`$${amount.toFixed(2)}`);
});

Diese Assertion ist in Ordnung, solange die Implementierung korrekt ist - aber das String-Template in der Assertion ist im Wesentlichen dieselbe Logik wie die getestete Funktion. Wenn die Funktion einen Bug hat (vielleicht rundet sie in manchen Locales falsch oder lasst das Dollarzeichen bei negativen Werten weg), missed dieser Test ihn, weil er dieselben Annahmen wie die fehlerhafte Implementierung macht.

Eine echte Verhaltens-Spezifikation wurde beschreiben, was der Output fur bestimmte Inputs sein soll - abgeleitet aus Produktanforderungen, nicht indem man die Funktion mental ausfuhrt und das Ergebnis aufschreibt.

Fehlermuster 4: Test-Isolation, die bei Integration versagt

Sprachmodelle mocken standardmassig alles. Das erzeugt Tests, die schnell, deterministisch und bedeutungslos sind. Wenn wir dieselbe Codebase gegen eine echte Datenbank oder einen echten HTTP-Client laufen lassen, erweisen sich bedeutende Anteile dieser Test-Annahmen als falsch.

Wir finden konsistent drei Kategorien von Integrationsfehlern, die durch ubermassig gemockte Unit-Tests verborgen werden. SQL-Queries, die gegen den Mock funktionieren, aber gegen das echte Schema fehlschlagen, weil der Mock keine Constraints oder Spaltentypen korrekt modelliert hat. HTTP-Clients, die Mock-Payloads zuruckgeben, die durch die Annahmen des Modells uber eine API geformt wurden statt durch das tatsachliche Response-Format der API. Service-Layer-Code, der eine bestimmte Transaktionsgrenze voraussetzt, die der Mock ignoriert.

Die Ironie ist, dass das Mocking oft ubermassig ist, gerade weil das Modell "gut isolierte" Unit-Tests produzieren wollte. Ein Code-Qualitats-Audit einer Vibe-Coded-Codebase wird fast immer empfehlen, einen bedeutenden Teil der mock-schweren Unit-Suite durch eine kleinere Anzahl von Integrationstests zu ersetzen, die tatsachlich die Vertrage zwischen Schichten validieren.

Fehlermuster 5: Test-Namen, die Code statt Verhalten beschreiben

Das ist individuell das am wenigsten schadliche Fehlermuster, aber das aufschlussreichste Signal fur KI-Testgenerierung im Grossen: Test-Namen wie testCalculateOrderTotal, should_return_value oder it handles the case. Das sind Namen, die einen Aufruf einer Funktion beschreiben statt ein Verhalten, von dem ein Nutzer oder System abhangt.

Gute Test-Namen sind Spezifikationen: invoice_total_includes_tax_when_customer_is_vat_registered, payment_fails_with_insufficient_funds_error, concurrent_updates_do_not_produce_negative_inventory. Wenn ein Test mit einem dieser Namen fehlschlagt, weisst du sofort, was kaputt ist und warum es wichtig ist. Wenn testCalculateOrderTotal fehlschlagt, musst du den Test lesen, um zu verstehen, was er uberhaupt gepruft hat.

Test-Namen sind kostenlose Dokumentation. KI-generierte Test-Suites nutzen sie systematisch zu wenig.

Warum Coverage-Zahlen nicht die Antwort sind

Die naturliche Reaktion auf all das ist, den Coverage-Schwellenwert zu erhohen. Wenn Tests oberflachlich sind, fordere mehr Coverage. Das ist der falsche Instinkt. Coverage misst, welche Code-Zeilen wahrend eines Test-Runs besucht wurden - es sagt nichts daruber aus, was verifiziert wurde. Eine Test-Suite, die jede Zeile besucht, indem jede Funktion mit einem einzigen Input aufgerufen wird und keine Assertions gemacht werden, kann 100% Coverage erreichen.

Die nutzlichen Metriken sind Branch Coverage und Mutation Score. Branch Coverage verlangt, dass jede Bedingung in der Codebase in beide Richtungen ausgewertet wurde. Mutation Testing fuhrt Hunderte kleiner automatisierter Anderungen am Quellcode durch - Vergleiche umkehren, Return-Statements entfernen, Operatoren tauschen - und pruft, ob deine Test-Suite jede Anderung erkennt. Wenn eine Mutation unentdeckt bleibt, hast du eine Test-Lucke. Diese Tools sind langsamer und gerauschvoller als Line Coverage, messen aber etwas Echtes.

Bevor du einer KI-generierten Test-Suite vertraust, lasse sie durch ein Mutations-Framework. Fur PHP ist Infection das Standard-Tool. Fur JavaScript, Stryker. Der Mutations-Score bei einem Vibe-Coded-Projekt landet beim ersten Durchlauf typischerweise zwischen 30-50%. Eine produktionstaugliche Suite sollte uber 70% liegen.

Wie eine vertrauenswurdige Test-Strategie aussieht

Nach dem Audit der bestehenden Tests baut ein Rettungs-Engagement die Test-Strategie typischerweise um drei Prinzipien herum neu auf.

Das erste ist Verhalten vor Implementierung. Tests sollten gegen die offentliche Schnittstelle einer Komponente geschrieben werden, spezifizierend, was die Komponente fur das System und seine Nutzer tut, nicht wie sie es tut. Die Implementierung kann refaktoriert werden ohne eine Test-Suite zu beruhren, die auf Verhalten basiert; eine Test-Suite, die auf der Implementierung basiert, muss jedes Mal umgeschrieben werden, wenn sich die Interna andern.

Das zweite ist proportionales Integrationstesten. Nicht alles muss ein isolierter Unit-Test sein. Fur Datenbankabfragen, fur Drittanbieter-API-Aufrufe, fur Datei-I/O: Integrationstests, die das echte System in einer kontrollierten Umgebung treffen, geben pro Zeile Test-Code weit besseren Regressionsschutz als Unit-Tests mit komplexen Mock-Setups. Ein Legacy-Code-Modernisierungs-Engagement schliesst fast immer das Ersetzen fragiler Mock-Turme durch schlanke, datenbankgestutzte Integrationstests ein, die die Vertrage zwischen Schichten validieren.

Das dritte ist explizite Fehlerspezifikation. Jeder Test fur Error-Handling-Code sollte verifizieren, dass der richtige Fehler fur den richtigen Input produziert wird - nicht nur, dass die Funktion etwas zuruckgibt ohne zu werfen. Die Fehlerpfade sind, wo echte Bugs leben.

Eine ehrliche Einschatzung bekommen

Wenn dein Projekt eine CI-Pipeline hat, die besteht, und eine Test-Datei-Anzahl, die gesund aussieht, aber mit wesentlicher KI-Unterstutzung gebaut wurde, enthalt die Test-Suite fast sicher einen bedeutenden Anteil der hier beschriebenen Fehlermuster. Das ist kein Grund zur Panik - es ist ein Grund fur eine strukturierte Prufung, bevor du einen Skalierungs-Meilenstein, ein Enterprise-Procurement oder eine Investor-Due-Diligence erreichst, die eine technische Prufung beinhaltet.

Der schnellste Weg zu einer ehrlichen Einschatzung ist, Mutation Testing auf einem reprasentativen Modul laufen zu lassen und den Score anzuschauen. Liegt er unter 50%, hast du ein Coverage-Theater-Problem. Ein Code-Qualitats-Consulting-Engagement kann eingrenzen, wie ein realistischer Fix aussieht, den Sanierungsaufwand schatzen und die Module priorisieren, bei denen Test-Lucken das grosste Produktionsrisiko tragen.

Wenn du besprechen mochtest, was du in deiner eigenen Codebase siehst, melde dich unter hello@wolf-tech.io oder besuche wolf-tech.io fur ein Gesprach. 30 Minuten reichen aus, um zu beurteilen, ob du ein Problem hast, das du jetzt losen solltest, oder eines, das warten kann.