Softwareentwicklung

Meine Tests sollen beweisen, dass meine Software nicht funktioniert

Wenn ich automatisierte Tests beschreibe, formuliere ich das Ziel inzwischen gerne etwas zugespitzt: Meine Tests sollen nicht beweisen, dass die Software funktioniert. Sie sollen versuchen nachzuweisen, dass sie nicht funktioniert.

Natürlich ist das kein mathematischer Beweis. Es beschreibt vielmehr die Haltung, mit der ich testen möchte. Mich interessiert weniger, ob eine Funktion unter idealen Bedingungen genau das tut, was ich beim Entwurf ohnehin erwartet habe. Interessant wird es dort, wo Annahmen nicht mehr gelten, Eingaben ungewöhnlich werden, Abläufe unterbrochen werden oder mehrere Dinge gleichzeitig passieren, an die ich während der eigentlichen Umsetzung vielleicht nicht gedacht habe.

Testing ist für mich deshalb eine eigene Perspektive auf Software, obwohl ich allein arbeite und es weder eine separate Testabteilung noch einen menschlichen Tester gibt. Genau darin liegt sogar eine besondere Schwierigkeit: Ich entwickle die Idee, treffe die Entscheidungen und kenne anschließend die Umsetzung. Wenn ich danach selbst prüfe, ob alles funktioniert, nehme ich dieses Wissen zwangsläufig mit in die Tests.

KI kann mir helfen, diese Perspektive zumindest teilweise aufzubrechen.

Wenn ich meine eigene Arbeit teste, kenne ich bereits die Lösung

Als allein arbeitender Entwickler kenne ich nicht nur das gewünschte Ergebnis, sondern meistens auch den Weg, auf dem es entstanden ist. Ich weiß, welche Stellen kompliziert waren, welche Annahmen ich getroffen habe und warum sich eine Funktion genau so verhält.

Das ist beim Testen nicht unbedingt hilfreich.

Ich kann natürlich bewusst Grenzwerte ausprobieren, falsche Eingaben erzeugen und Sonderfälle durchspielen. Trotzdem neige ich genau wie jeder andere Mensch dazu, in den Bahnen zu denken, die ich beim Entwurf bereits benutzt habe. Manche Abläufe erscheinen mir selbstverständlich, weil ich ihre interne Logik kenne, während ein Benutzer später möglicherweise etwas vollkommen anderes ausprobiert.

Mit automatisierten Tests möchte ich deshalb nicht nur meine eigenen offensichtlichen Prüffälle schneller ausführen. Ich möchte zusätzliche Situationen finden, die ich selbst vielleicht gar nicht aufgeschrieben hätte.

Heute erzeugt und prüft Codex bereits automatisierte Testfälle

In meinem aktuellen Entwicklungsprozess übernimmt Codex nicht nur Teile der Implementierung, sondern kann anschließend auch automatisierte Tests erstellen und ausführen. Zusätzlich kann ein spezialisierter Test-Agent eingesetzt werden, der technisch weiterhin innerhalb dieses KI-gestützten Prozesses arbeitet, aber einen anderen Auftrag bekommt.

Dieser Test-Agent ist keine Person und auch kein Ersatz für einen menschlichen Kollegen, den es bei mir ohnehin nicht gibt. Er ist schlicht eine technische Rolle innerhalb meiner Automatisierung, deren Aufgabe darin besteht, die fertige Umsetzung möglichst kritisch zu prüfen.

Dabei soll nicht nur der offensichtliche Standardfall getestet werden. Ich möchte Grenzwerte, ungültige Eingaben, widersprüchliche Zustände, unerwartete Reihenfolgen und Situationen prüfen lassen, in denen mehrere Abläufe gleichzeitig stattfinden.

Das Ziel ist nicht, möglichst schnell eine grüne Anzeige zu bekommen. Das Ziel ist, möglichst früh einen Grund dafür zu finden, warum diese Anzeige eigentlich rot sein müsste.

Ein grüner Test beweist erstaunlich wenig

Wenn zwanzig automatisierte Tests existieren und alle erfolgreich durchlaufen, sieht das zunächst gut aus. Trotzdem bedeutet dieses Ergebnis nur, dass genau die zwanzig geprüften Situationen das erwartete Resultat geliefert haben.

Es sagt nicht, dass die Anwendung insgesamt richtig funktioniert.

Vielleicht fehlt der entscheidende Grenzfall. Vielleicht wird eine falsche Annahme sowohl in der Implementierung als auch im Test verwendet. Vielleicht prüft ein Test lediglich, ob kein technischer Fehler auftritt, obwohl das fachliche Ergebnis falsch ist.

Gerade KI macht es sehr leicht, in kurzer Zeit eine große Menge Tests zu erzeugen. Das kann hilfreich sein, aber die Zahl der Tests ist für mich kein Qualitätsmerkmal, solange ich nicht weiß, welche Fehler sie tatsächlich entdecken würden.

Hundert grüne Tests können weniger wert sein als fünf sehr gute Tests, die genau dort hinschauen, wo die Umsetzung wahrscheinlich scheitern kann.

Das eigentliche Problem ist die fehlende Unabhängigkeit

Mit meinem heutigen Ansatz bin ich deshalb noch nicht vollständig zufrieden.

Codex setzt eine Funktion um. Danach werden mit Codex beziehungsweise einem darauf basierenden Test-Agenten Tests erzeugt und ausgeführt. Damit existieren zwar unterschiedliche technische Aufgaben, aber noch keine wirklich unabhängigen Denkweisen.

Wenn Codex eine Anforderung falsch interpretiert, kann dieselbe grundlegende Technologie bei der Testgenerierung genau dieselbe falsche Interpretation übernehmen.

Ein einfaches Beispiel zeigt das Problem. Eine bestimmte Aktion soll nur unter klar definierten Voraussetzungen erlaubt sein. Codex versteht diese Voraussetzungen falsch und implementiert eine technisch saubere Lösung, die fachlich dennoch nicht der Anforderung entspricht. Anschließend entstehen automatisierte Tests, die aus derselben falschen Interpretation ableiten, welches Verhalten erwartet wird.

Die Tests sind erfolgreich.

Die Software ist trotzdem falsch.

Genau diese Art von Fehler ist für mich problematischer als ein offensichtlicher Absturz, weil alles zunächst vollkommen plausibel aussieht.

Eine andere KI wäre wahrscheinlich sinnvoller, aber nicht automatisch ausreichend

Langfristig möchte ich das Testing deshalb stärker von der Implementierung trennen. Naheliegend wäre, dafür ein anderes KI-System oder zumindest ein anderes Modell einzusetzen, das nicht exakt dieselben typischen Schlussfolgerungen zieht wie die KI, die zuvor den Code erzeugt hat.

Das würde die Unabhängigkeit erhöhen, löst das Problem aber nicht vollständig.

Wenn die zugrunde liegende Spezifikation missverständlich ist, können zwei unterschiedliche KI-Systeme schließlich denselben fachlichen Fehler machen. Unabhängigkeit entsteht deshalb nicht allein dadurch, dass auf dem zweiten System ein anderer Modellname steht.

Entscheidend ist auch, welche Informationen die Tests bekommen und woraus das erwartete Verhalten abgeleitet wird.

Die Tests sollten möglichst unabhängig vom Lösungsweg entstehen

Für mich bedeutet das, dass die Testgenerierung nicht einfach die komplette Entstehungsgeschichte der Implementierung übernehmen sollte.

Codex darf für die Umsetzung das bestehende Repository analysieren, technische Entscheidungen treffen und einen passenden Lösungsweg entwickeln. Die automatisierten Tests sollten sich dagegen möglichst stark an der fachlichen Spezifikation orientieren und prüfen, ob das sichtbare Verhalten tatsächlich dazu passt.

Die ausführliche Konzeptphase vor der Implementierung bekommt damit noch eine zweite wichtige Funktion. Die daraus entstandene Spezifikation dient nicht nur als Arbeitsgrundlage für die Umsetzung, sondern kann anschließend unabhängig davon beschreiben, welches Verhalten getestet werden muss.

Je weniger die Tests wissen müssen, warum Codex eine bestimmte interne Lösung gewählt hat, desto größer ist die Chance, dass sie eine Abweichung zwischen gewünschtem und tatsächlichem Verhalten erkennen.

Auch meine Tests müssen selbst geprüft werden

Damit entsteht allerdings sofort die nächste Frage: Woher weiß ich, ob die automatisierten Tests selbst gut genug sind?

Ein Test kann genauso falsch sein wie der eigentliche Programmcode. Er kann etwas Unwichtiges prüfen, den entscheidenden Fehler übersehen oder nur scheinbar eine fachliche Aussage treffen.

Ein Verfahren, das deshalb gut zu meiner Denkweise passt, ist Mutation Testing. Dabei wird der Programmcode absichtlich leicht verändert, sodass gezielt Fehler eingebaut werden. Anschließend wird geprüft, ob die vorhandenen Tests darauf reagieren.

Die Fragestellung verändert sich dadurch deutlich.

Nicht nur:

„Laufen meine Tests erfolgreich?“

Sondern:

„Werden meine Tests zuverlässig rot, wenn ich die Software absichtlich kaputtmache?“

Wenn beispielsweise eine wichtige Bedingung im Code umgedreht wird und trotzdem alle Tests grün bleiben, weiß ich ziemlich sicher, dass meine Tests an dieser Stelle weniger aussagekräftig sind, als ich angenommen hatte.

KI kann gründliches Testing für mich überhaupt erst wirtschaftlich machen

Als Einzelunternehmer ist meine Zeit zwangsläufig begrenzt. Neben der eigentlichen Entwicklung kümmere ich mich selbst um Konzeption, Betrieb, Kunden, Vertrieb, Administration und all die anderen Dinge, die in einem Unternehmen anfallen. Ich kann deshalb nicht nach jeder kleinen Änderung stundenlang dutzende Varianten manuell testen.

Genau an dieser Stelle kann KI einen großen Vorteil bringen.

Wenn automatisierte Agenten zusätzliche Testfälle erzeugen, durchführen und auswerten können, wird ein Umfang an Testing möglich, den ich allein manuell kaum wirtschaftlich durchführen könnte. Die Automatisierung ersetzt dabei nicht mein Nachdenken über Qualität, sondern verlagert meine Arbeit stärker auf die Frage, was geprüft werden sollte und nach welchen Kriterien ein Ergebnis als korrekt gilt.

Die eigentliche Fleißarbeit kann anschließend weitgehend automatisch stattfinden.

Testabdeckung ist für mich nicht das eigentliche Ziel

Eine hohe Code Coverage kann nützlich sein, aber sie sagt nur begrenzt etwas darüber aus, ob die wichtigen Fehler tatsächlich entdeckt werden.

Mich interessieren deshalb stärker konkrete Fehlerklassen.

Was passiert bei fehlenden Eingaben, extremen Werten oder doppelten Anfragen? Wie reagiert die Anwendung auf unterbrochene Abläufe? Was passiert nach einem Neustart? Wie verhält sich eine Funktion, wenn ein abhängiges System plötzlich nicht erreichbar ist? Was geschieht, wenn Dinge in einer Reihenfolge passieren, die beim Entwurf niemand vorgesehen hat?

Und besonders wichtig: Was passiert, wenn Codex eine Anforderung auf eine Weise umgesetzt hat, die technisch plausibel, aber fachlich falsch ist?

Genau für solche Situationen möchte ich meine Tests entwickeln.

Das Testing ist für mich noch eine offene Baustelle

Ich habe inzwischen einen funktionierenden automatisierten Ansatz, mit dem Codex Tests erzeugen und ein spezialisierter Test-Agent zusätzliche Fehlerfälle suchen kann. Damit bin ich deutlich weiter als bei einem rein manuellen Vorgehen, trotzdem halte ich das Problem noch nicht für gelöst.

Langfristig möchte ich die Unabhängigkeit zwischen Implementierung und Prüfung weiter erhöhen und gleichzeitig herausfinden, wie sich auch die Qualität der Tests automatisiert bewerten lässt.

Vielleicht entsteht daraus irgendwann ein mehrstufiger Prozess, in dem eine KI die Umsetzung übernimmt, ein anderes System unabhängig Testfälle entwickelt und anschließend weitere automatisierte Verfahren prüfen, ob diese Tests tatsächlich Fehler erkennen können.

Für einen allein arbeitenden Entwickler klingt das zunächst nach einer erstaunlich großen Testorganisation. Der entscheidende Unterschied ist allerdings, dass dafür keine zusätzlichen Menschen notwendig sind.

Und genau deshalb kann ich mir heute einen Qualitätsprozess aufbauen, dessen Aufwand für mich früher schlicht unrealistisch gewesen wäre.