AI-gestütztes Testing: Unit-Tests als Sicherheitsnetz für KI-generierten Code
Categories:
AI-gestütztes Testing löst kein Problem der Testabdeckung, sondern mein Vertrauen in den von KI generierten Code. KI kann in drei Minuten einen großen Code-Abschnitt ausgeben, während der Mensch zwei Stunden braucht, um ihn zu prüfen. Drei Minuten Generierung, zwei Stunden Prüfung – das ist keine Übertreibung. Meine Lösung besteht darin, den Prüfungsgegenstand zu wechseln: Ich lese nicht Zeile für Zeile den Code, sondern stattdessen die Fallbeschreibungen (Testdokumente) und baue mit Unit-Tests eine Gate im lokalen Entwicklungsumgebung, sodass fehlerhafte Cases für mich die Faulheit und Fehler der KI überwachen.
##小 bug 才是 AI 辅助测试的主战场
Bugs lassen sich in zwei Kategorien einteilen: leicht zu lösende Bugs und schwer zu lösende Bugs.
Schwer zu lösende Bugs entstehen typischerweise aus Mängeln in der Architektur oder dem Konzeptdesign, entwickeln sich allmählich zu einer historischen Last, bei der ein einziger Faktor vieles beeinflusst; sie können auch durch Dritte verursacht werden, wie z. B. Drittanbieterbibliotheken, Betriebssysteme oder Compiler, und erfordern spezielleres Wissen und tiefere Untersuchungen zur Lösung. Diese Art komplexer Probleme betrifft nicht den Umfang dieses Artikels.
Der vorliegende Artikel konzentriert sich auf kleine Bugs im Lebenszyklus eines Produkts und nutzt KI-gestützte Erstellung von Testcases, um folgende Probleme zu lösen:
- Bugs, die schnell entdeckt werden können
- Niedrigrangige Bugs, die mit minimalen Änderungen behoben werden können
- Durch Änderungen eingeführte neue Bugs
- Fehler unter Zeitdruck bei Eingriffen
- Abwägung von Kosten und Effizienz gezielter Tests
Die meisten Bugs sind kleine Bugs, aber kleine Bugs können auch schwerwiegende Probleme verursachen. Wenn man kleine Bugs lokal löst, spart man eine Menge nachfolgender Energie.
##把检视对象从代码换成用例文档
Ich hatte einmal eine sehr kühne Idee: Ich ließe die KI eine Anforderung vollständig umsetzen, ohne den generierten Code überhaupt zu prüfen, ähnlich wie der Copilot Agent Modus, bei dem man komplett loslässt. Später habe ich davon Abstand genommen. Der grundlegende Grund war, dass ich mir unsicher war.
Für ein logisch strenges Binärprogramm, bei dem 0 und 1 klar voneinander getrennt sind, bedeutet Unsicherheit, das Vertrauen von Upstream und Downstream zu verlieren, das Vertrauen der Kunden zu verlieren. Also lerne ich, über Vibe Coding nachzudenken: Bei KI-generiertem Code muss man sich sicher sein.
Das Problem liegt in den Prüfungskosten. Die von der KI generierte oder modifizierte Code-Menge ist manchmal zu groß. Drei Minuten Generierung, zwei Stunden Prüfung – das ist keine Übertreibung. Der Aufwand für Testcode ist typischerweise höher als für Geschäftscore, und er wird möglicherweise nicht in die Lieferbewertung der Forschung und Entwicklung einbezogen. Dies ist einer der Gründe, warum viele Menschen nicht aktiv Tests schreiben wollen. Aber dass der KI-Assistent intelligenter ist als ich, ist inzwischen eine Tatsache. Anstatt dagegen zu kämpfen, sollte ich ihm ein Gate einrichten.
Den Prüfungsgegenstand von Code zu Fallbeschreibungen zu wechseln, ist der Schlüssel, um diesen Knoten zu durchschneiden. Im Gegensatz zum Geschäftscore beschreiben KI-generierte Testcases typischerweise eine sehr spezifische Situation, die leicht zu prüfen und zu validieren ist. Viele Cases müssen nicht einmal Zeile für Zeile gelesen werden; man kann einfach warten, bis sie Fehler melden, und sie dann genauer ansehen. Zu behaupten, man würde KI-Code überhaupt nicht prüfen, ist eine Übertreibung. Aber mit Hilfe von vollständigen Fallbeschreibungen kann man beweisen, dass nicht jede Code-Zeile eine manuelle Review erfordert.
---
title: Von der Code-Prüfung zur Dokumenten-Prüfung
---
flowchart TB
A[KI generiert großen Code-Umfang] e1@--> B[Zeilenweise Prüfung ist teuer]
B e2@--> C{Was prüfen}
C e3@-->|Code| D[Zwei Stunden Prüfung, unsicher]
C e4@-->|Fall-Dokumente| E[Leicht zu prüfen, schnelle Validierung]
E e5@--> F[Unit-Test-Gate]
F e6@--> G[Fehlerhafte Cases offenbaren Probleme]
classDef risk fill:#FFF3E0,stroke:#E65100,color:#E65100;
classDef ok fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20;
classDef animate stroke:#EF6C00,stroke-width:2px,stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class D risk;
class E,F,G ok;
class e1,e2,e3,e4,e5,e6 animate;Methodenboden: Sechs Sigma und DMAIC
Six Sigma ist eine datengetriebene Methodik zur Qualitätsverbesserung. Das Kernziel besteht darin, die Defektrate durch Reduzierung von Prozessvariationen auf 3.4 pro Million zu kontrollieren, also das 6σ-Niveau1.
Es liefert einen ausführbaren Prozess DMAIC:
- Define Definition: Anforderungsanalyse, Umwandlung in Datenmodelle, Entwurf von Datenstrukturen und Schnittstellen
- Measure Messung: Case-Überwachung, Abdeckung, Leistung, Sicherheit
- Analyze Analyse: Analyse von Cases und Geschäftscore
- Improve Verbesserung: Verbesserung von Cases und Geschäftscore
- Control Kontrolle: Automatisiertes Test-Gate
Zugehörige technische Tools umfassen Linter, statische Analyse, dynamische Analyse und Testframeworks. Es gibt viele Codequalitätsindikatoren, die man beachten kann, wie Komplexitätsindikatoren (zyklomatische Komplexität, kognitive Komplexität, Verschachtelungstiefe, Klassenkopplung), Qualitätsindikatoren (Duplicationsrate, Funktionslänge, magische Zahlen) sowie Testindikatoren (Unit-Test-Abdeckung, Integrationstest-Erfolgsrate, Case-Validität, Exception-Abdeckung).
Methoden und Tools können helfen, Probleme zu finden und zu lösen. Der Preis ist möglicherweise ein Anstieg der Lieferkosten und eine Verringerung der Effizienz in gewissem Maße. Bei begrenzten Ressourcen muss man eine Methode wählen, die kurz-, mittel- und langfristig kontinuierliche Vorteile bringt. Meine Wahl ist die Nutzung von Unit-Tests als Hilfsmittel im Entwicklungsprozess.
Durch die Nutzung der KI beim Sprachwechsel zwischen verschiedenen Rollen kann die Entwicklung die Vorteile von TDD, BDD und ATDD verbinden, Cases und Testcode schnell generieren und die Lieferqualität verbessern.
Testgetrieben: TDD/BDD/ATDD und testbarer Code
Die Unterschiede zwischen den drei testgetriebenen Ansätzen sind wie folgt:
| Dimension | TDD | BDD | ATDD |
|---|---|---|---|
| Triebquelle | Unit-Test | Nutzerverhaltens-Szenarien | Annahmebedingungen |
| Teilnehmer | Entwickler | Geschäft + Entwicklung | Geschäft + Test + Entwicklung |
| Testebene | Unit-Test | Integration/End-zu-End-Test | Annahmetest |
| Tool-Beispiele | JUnit, pytest | Cucumber, SpecFlow | FitNesse, Robot Framework |
| Typische Ausgabe | Code-Abdeckungsbericht | Ausführbare Nutzer-Szenarien-Dokumente | Annahmetest-Erfolgsrate |
| Sprache | assert add(2,3) == 5 | Natürliche Sprache (Given-When-Then) | Geschäftssprache (Annahmebedingungen-Tabelle) |
Testbarer Code hat vier Kernprinzipien: Single Responsibility (jede Funktion macht nur eines), Dependency Injection (Abhängigkeiten werden über Parameter übermittelt), Interface Segregation (Ein- und Ausgabetypen sind klar), State Observability (Bereitstellung von Abfrage-Schnittstellen zur Validierung internen Zustands).
In den Prompts für die KI schlage ich vor, direkt zu schreiben: erweiterbar, Single Responsibility, Dependency Injection.
Ich habe versucht, die KI die Länge der generierten Funktionen und Dateien kontrollieren zu lassen, aber ihre Befolgung war schlecht; nach dem Wechsel zu Single Responsibility und Dependency Injection war der generierte Code deutlich mehr den Erwartungen angemessen.
Frühe Optimierung ist die Wurzel allen Übels. Bei der Erstellung grundlegender Datenstrukturen und Schnittstellen kann man die KI darauf hinweisen, Erweiterbarheit priorisieren.
Codebeispiel für leicht testbaren Code:
// user.h
typedef struct {
int id;
char name[50];
int age;
} User;
// Speicher-Schnittstellen-Abstraktion
typedef struct {
User* (*get_user)(int id);
int (*save_user)(const User* user);
} UserStorage;
// Benutzer erstellen (Dependency Injection der Speicher-Schnittstelle)
User* create_user(const char* name, int age, UserStorage* storage) {
if(strlen(name) >= 50 || age <= 0) return NULL;
User* new_user = malloc(sizeof(User));
new_user->id = generate_user_id();
strncpy(new_user->name, name, 49);
new_user->name[49] = '\0';
new_user->age = age;
if(storage->save_user(new_user) != 0) {
free(new_user);
return NULL;
}
return new_user;
}
Codebeispiel für schwer testbaren Code:
// Eng gekoppelte Datenbank-Operation
void save_user_to_db(User* user) {
MYSQL conn;
mysql_init(&conn);
if(!mysql_real_connect(&conn, "localhost", "root", "pass", "test", 0, NULL, 0)) {
log_error("DB connection failed"); // Schwierig zu testende Log-Abhängigkeit
exit(1); // Direktes Beenden beeinträchtigt Testausführung
}
// SQL zusammenstellen und in Datenbank schreiben
}
Guter Code wirkt unauffällig, schlechter Code macht sich stark bemerkbar. Funktionen, die leicht zu testen sind, haben klare Rückgabewerte, standardmäßige Fehlerbehandlung, klare Eingabeparameter, isolierte Nebeneffekte und unterstützen Dependency Injection.
Der von der KI gelieferte Code ist manchmal nicht Best Practice und weist auch Stilprobleme auf. Obwohl sie oft Probleme lösen kann, ist ein reales Projekt kein einmaliges Geschäft; man muss die spätere Wartung berücksichtigen. Beim Anweisen der KI zur Codegenerierung sollte man ebenfalls betonen: Lesbarkeit voran, erweiterbar, Single Responsibility, Dependency Injection. Zum Thema Code-Stil werden wir nicht weiter diskutieren; man kann den Google Code Style Guide konsultieren2.
Praktische Entwicklungshindernisse und Abwägungen
Anforderungen durchlaufen bei der Übergabe zwischen verschiedenen Rollen mehrere Sprachkonvertierungsrunden, und jede Runde kann zu Abweichungen in der Formulierung führen. Zum Beispiel verstand der Backend-Entwickler „zentrale Ansicht" des Kunden als „einzelne API-Antwort" ohne Paginierung. Durch die Nutzung von KI zur Generierung von Sprachvorlagen für verschiedene Rollen kann man den Informationsverlust bei manuellen Konvertierungen verringern.
Unüberlegte Implementierung ist die Ursache vieler Bugs. Mit Hilfe der KI bei der Caseszenario-Entwicklung kann sie nicht nur die erwartete Aufgabe erledigen, sondern auch überraschende Anregungen liefern und Details auffüllen, die der Herausgeber nicht bedacht hatte. In jeder Rolle des Sprachkonvertierens kann man mit der KI dialogisch Schwächen ausräumen. Markdown ist die Sprache, die die KI am leichtesten versteht; Ein- und Ausgabe sollten möglichst im Markdown-Format gehalten werden.
Case-Code hat mehrere Punkte, die man beachten muss: Case-Trennung, Genauigkeit simulierter Abhängen, Validität von Assertions, Randwerte, Exception-Behandlung, Leistung, Parallelität. Die KI wird faul sein und nicht alle Szenarien auf einmal abdecken. Die Generierung des gesamten Testcodes auf einmal fordert viel vom Modell; man kann es in mehreren Schritten machen.
Am Beispiel des Claude-Modells in Cursor: Es bewältigt bestimmte Aspekte des Case-Codes gut, vergisst aber oft Case-Trennung und Exception-Szenarien. Das Hinzufügen einer User-Regel kann Faulheit etwas eindämmen: Bei der Generierung von Testcases auf Case-Dokumentation und Case-Trennung achten. Dennoch wird es nicht alle Szenarien auf einmal generieren; normale Pfade, Exception-Pfade, Randwerte, Leistung, Parallelität etc. müssen durch mehrere Dialoge ergänzt werden.
Ein machbarer AI-gestützter Testing-Workflow
In Kombination mit dem Designdokument kann man eine Pipeline von Szenarien zu Cases aufbauen:
---
title: AI-gestütztes Testing-Workflow
---
flowchart TB
A[Designdokument] e1@--> B[Generiere Annahmetest-Szenarien]
B e2@--> C[Generiere Datenstrukturen und Schnittstellen]
C e3@--> D[Generiere Client-Seitige Case-Tabelle]
D e4@--> E[Generiere Case-Code]
D e5@--> F[Generiere Geschäftscore]
E e6@--> G[Lokales Gate: Cases bestanden]
F e7@--> G
G e8@--> H{Fehlerhafte Cases vorhanden}
H e9@-->|Ja| E
H e10@-->|Nein| I[Testing einreichen]
classDef input fill:#E3F2FD,stroke:#1565C0,color:#0D47A1;
classDef gen fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20;
classDef gate fill:#FFF3E0,stroke:#E65100,color:#E65100;
classDef animate stroke:#EF6C00,stroke-width:2px,stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class A input;
class B,C,D,E,F gen;
class G,H,I gate;
class e1,e2,e3,e4,e5,e6,e7,e8,e9,e10 animate;Die Prompts für die einzelnen Schritte können wie folgt organisiert werden: Basierend auf dem Produkt-Designdokument generiere Annahmetest-Szenarien; basierend auf den Testszenarien generiere Datenstrukturen und Schnittstellen; basierend auf Testszenarien und Schnittstellen generiere clientseitige Case-Tabellen; basierend auf Schnittstellen und Case-Tabellen generiere Case-Code; basierend auf den Testszenarien generiere Geschäftscore und stelle sicher, dass die Cases bestehen.
Hier gibt es ein verstecktes Risiko: Die KI kann sowohl Geschäftscore basierend auf Testcode ändern als auch umgekehrt Testcode basierend auf Geschäftscore. Wenn auf beiden Seiten Fehler existieren, wird die KI verwirrt werden. Mit zunehmender Dialogzahl werden Testcode und Geschäftscore immer chaotischer; zu diesem Zeitpunkt muss manuell eingegriffen werden.
Ein zu ambitionierter Prompt ist ein Gegenbeispiel: Die Anforderung, die KI soll auf einmal normale, Exception-, Randwerte und Parallelität abdecken und Code generieren. Solche Anfragen brechen leicht ab. Die korrekte Vorgehensweise besteht darin, Szenarien in Cursor aufzuteilen und in mehreren Anfragenchargen zu generieren. Performance-Testcases benötigen große Datenmengen und sollten in die Systemtest-Phase gestellt werden; zuerst lässt man die KI einen Zufallsdatengenerator schreiben, dann nutzt man die generierten Daten zur Caseschaffung.
Integrationstests erfordern den Aufbau einer Testumgebung. Einige Cases hängen von externen Tools ab, wie z. B. Mikroisolation, die iptables und ipset benötigt; man kann Testcontainer mit Docker bauen. Das Prinzip ist: Wie der Mensch testet, so kommandiert man die KI, dieselben automatisierten Schritte zu realisieren. Bezüglich Sicherheitstests stützt sich derzeit hauptsächlich auf Erfahrung und Analyse; es gibt noch keine reifere KI-gestützte Erfahrung.
Zusammenfassung
Dieser Workflow im Vergleich zur Nutzung ohne KI erzeugt mehr Dokumente und Testcode, und es ist schwierig, jedes Wort zu prüfen. Aber Dokumente sind leichter zu prüfen als Code, insbesondere Case-Tabellen, die eine genaue Anleitung für die Case-Generierung bieten. In der实际 Entwicklung prüfe ich manchmal nur die fehlerhaften Cases und bestätige Probleme durch das Debuggen fehlerhafter Cases.
Es gibt drei Verbesserungsrichtungen: Der Entwurfs-Subprozess hat Überschneidungen; Annahmetest-Szenarien können von Datenstrukturen und Schnittstellen abhängen; die clientseitige Case-Tabelle sollte vom Entwicklungsteam selbst geprüft werden; vor der Testeinreichung müssen erneut detaillierte Testszenarien mit Geschäftscore-Details generiert werden.
Der endgültige Nutzen liegt nicht in der Testabdeckungszahl, sondern im Vertrauen: Durch das Lokale Gate der Unit-Tests kann ich endlich sicher sein, wenn es um KI-generierten Code geht, anstatt den vibing-Code ohne Nachdenken in die Produktion zu schicken.
Six Sigma – Definition und DMAIC-Methodik basieren auf dem Wikipedia-Artikel. ↩︎
Google Style Guide ist der öffentliche Code-Stil-Leitfaden von Google. ↩︎