Codequalität und Zuverlässigkeit

App: Python Course · Datum: 1. September 2026

Umfang: der eingebaute Python-Interpreter, die verfassten Kursinhalte (2194+ Übungen, 48 Lektionen, 17 Module, ein neuer Abschnitt „Lifehacks" — 15 Karten, 6 Sprachen), der Algorithmus für die Wiederholungsplanung (Spaced Repetition) und — neu in diesem Bericht — ob die Testsuite selbst Regressionen wirklich erkennt (Mutationstests) für die 5 kritischsten Logik-Module

Kurz gesagt: zwei getrennte Fragen, zwei Antworten. Ist der Code korrekt? 9710 Kandidaten gegen CPython und andere Orakel geprüft — 0 Abweichungen. Schützen die Tests das auch wirklich? 3694 absichtlich eingeschleuste Defekte (Mutationstests — die Abdeckung wuchs mit dem Code mit) — alle 3694 erkannt. Die zweite Frage ist neu in diesem Bericht — sie macht die erste erst vertrauenswürdig, statt nur behauptet.

0 von 9710
Abweichungen gegenüber CPython/Orakeln
3694 von 3694
eingeschleuste Defekte von Tests erkannt (Mutation Score 100%)

Mutationstests — erkennen die Tests Bugs wirklich

Ein Coverage-Prozentsatz beantwortet nur „ist diese Zeile mindestens einmal ausgeführt worden", nicht „würde ein Test bemerken, wenn diese Zeile kaputtgeht". Mutationstests beantworten genau die zweite Frage: Ein Werkzeug schleust absichtlich Tausende kleiner, einzelner Defekte in den Code ein (ändert < zu <=, && zu ||, entfernt eine Grenzprüfung usw.) und prüft, ob danach mindestens ein Test fehlschlägt. Erkennt kein Test den eingeschleusten Defekt, ist das eine echte Lücke — selbst wenn die Zeilenabdeckung für sich genommen 100 % anzeigt.

Modul Mutanten Erkannt Score
Logik zum Freischalten von Lektionen (gate-engine) 208 208 100.00%
Erfolge, XP, Serie, Herzen (gamification-engine) 267 267 100.00%
Wiederholungsplaner (srs-engine) 47 47 100.00%
Python-Interpreter (Tokenizer, Parser, Ausführung — inklusive Import-/Datei-Code aus Modul 17) 3123 3123 100.00%
Abschnitt „Lifehacks" (lifehacks-engine + manage-lifehacks) 49 49 100.00%
Zusammen, 5 kritischste Module 3694 3694 100.00%

„Erkannt" bedeutet hier: Der Mutant hat mindestens einen Test fehlschlagen lassen, oder eine Endlosschleife/ein Hängenbleiben verursacht (zählt ebenfalls — der Defekt wurde trotzdem eindeutig sichtbar, nur auf andere Weise; die Python-Interpreter-Zeile oben zählt genau einen solchen Fall auf diese Weise). Jeder Einzelfall, in dem ein Mutant zunächst überlebte, wurde von Hand geprüft: Entweder wurde ein neuer Test geschrieben, der das tatsächliche Verhalten dokumentiert, oder es wurde mathematisch bewiesen, dass genau diese Codeänderung das Ergebnis niemals beeinflussen kann (und in dem Fall trägt die Zeile selbst einen erklärenden Kommentar, kein stilles Übergehen), oder — wo sich das als die treffendere Antwort erwies — der Code selbst wurde so vereinfacht, dass der nicht unterstützte Logikzweig schlicht aufhörte zu existieren. Einer dieser Fälle wird weiter unten im Detail durchgegangen.

Methodik — Korrektheit des Codes (CPython-Abgleich)

Drei unabhängige Prüfstränge, ohne gemeinsamen Code und ohne Abhängigkeit der Ergebnisse voneinander. Interpreter: Die App führt Code über einen eigenen, von Grund auf neu geschriebenen Python-Interpreter aus (kein CPython, keine Drittanbieter-Bibliothek) — jeder Kandidat (eine verfasste Referenzlösung, eine aus einer Vorlage generierte Instanz oder ein zufällig generiertes Programm) wurde gleichzeitig im Interpreter der App und in einem systemseitig installierten CPython 3.12.3 ausgeführt und anschließend auf exakte Ergebnis- bzw. Fehler-Konsistenz verglichen. Kursstruktur: ein vollständiger, ungestichprobter Durchlauf über alle Inhaltsdateien. Wiederholungsplaner: seine Ausgabe wurde mit einer separat geschriebenen (nicht aus dem Planer selbst kopierten) Implementierung derselben mathematischen Formel abgeglichen. Die Validierung lief über ein temporäres, isoliertes Werkzeug außerhalb des eigentlichen App-Codes, in mehreren unabhängigen Runden, die sich nicht im Voraus auf die Ergebnisse der jeweils anderen stützten.

Ergebnisse gegen ein Orakel

Prüfstrang Geprüft Übereinstimmend Abweichungen
Interpreter — Kursinhalte und erweiterter Übungsmodus (Referenz- und alternative Lösungen, Lückentext-Aufgaben im Wertemodus, statische und parametrisierte Vorlagen des erweiterten Übungsmodus, die reale Ausführung von import/open() aus Modul 17) sowie der Abschnitt „Lifehacks" (Vorher-/Nachher-Code-Schnappschüsse) 393 393 0
Interpreter — Zufallstests (Fuzzing) (20 Konstrukt-Kategorien) 400 400 0
Wiederholungsplaner (1020 Szenarien, 8917 Wiederholungsschritte) 8917 8917 0
Kombiniert 9710 9710 0

Struktur- und Inhaltsprüfungen

Prüfung Abdeckung Gefundene Probleme
Kursstruktur (Gültigkeit der Lösungen, Integrität des Voraussetzungsgraphen, Eindeutigkeit der IDs) 2194 Übungsdateien, 48 Lektionen, 17 Module 0
Struktur des erweiterten Übungsmodus-Pools 64 Pool-Einträge (53 statisch + 11 Vorlagen) 0
Abschnitt „Lifehacks" (jede Karte verweist auf ein reales Kurskonzept; Vorher-/Nachher-Code liefert im echten Interpreter identische Ausgabe) 15 Karten, 30 Code-Schnappschüsse 0
Lokalisierungsparität (Inhalts-Schlüssel, keine unübersetzten Platzhalter) 12 214 Schlüssel × EN, ES, FR, DE, PT (61 070 Vergleiche) 0

Werkzeuge

Stryker Mutator — Mutationstests (Erzeugung und Ausführung Tausender absichtlicher Defekte gegen die bestehende Testsuite).
Vitest — Test-Runner (1112 Unit-/Integrationstests zum Zeitpunkt dieses Berichts).
TypeScript, strenger Modus — strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes.
CPython 3.12.3 — externes Orakel zur Prüfung des Interpreter-Verhaltens.
Eigene isolierte Skripte — Fuzz-Test-Erzeugung und Abgleich des Wiederholungsplaners, außerhalb des eigentlichen App-Codes.

Wie diese Zahlen wirklich erreicht wurden — ohne Beschönigung

Diese Zahlen sind nicht der Ausgangspunkt gewesen. Der erste Mutationstest-Durchlauf am größten und schwierigsten Modul (dem Python-Interpreter, ~2500 Zeilen) ergab 75,65 % — knapp ein Viertel der absichtlich eingeschleusten Defekte blieb also völlig unbemerkt. Diese Lücke auf 100 % zu schließen, brauchte sieben aufeinanderfolgende Durchläufe, die jeweils einen Teil des Rests schlossen und dabei neue Grenzfälle offenlegten — einschließlich Methodikfehlern, die während des Prüfprozesses selbst gemacht wurden.

Echte Anwendungsfehler gefunden und behoben (nicht nur Testlücken) — genau durch diesen gründlichen Prozess aufgedeckt: Die Punktzahl einer Lektion wurde nach einer korrigierten Falschantwort fälschlich zu niedrig angesetzt (ein einziger Fehler begrenzte die maximal mögliche Punktzahl dauerhaft, selbst wenn die Lernperson beim Wiederholen sofort richtig antwortete); der Einstufungstest vergab fälschlich die Erfolge „Fehlerfrei" und „Keine Gnade" für übersprungene, nicht wirklich abgeschlossene Lektionen; sowie drei eng begrenzte Bugs im Interpreter selbst (Ganzzahldivision, Rundung beim Restwert-Operator, Vergleich von bool mit int).

Ein Methodikfehler wurde mitten im Prozess entdeckt und korrigiert: Eine zwischenzeitliche Verifikationsmethode (Ersetzen des gesamten Zeilentexts statt Mutation an Strykers exakten Koordinaten) überschätzte, wie viele Mutanten tatsächlich „äquivalent" (also unlösbar) waren — von 35 solchen Annahmen bestätigte sich nur 1. Behoben durch Umstellung auf präzise, koordinatengenaue Verifikation für jeden Einzelfall.

Die letzten 5 Mutanten (String-Vergleich für das Sortieren von Listen) widerstanden sowohl einem Äquivalenzbeweis als auch jedem Test über die normale öffentliche Schnittstelle — der native Sortieralgorithmus von JavaScript führt redundante Vergleiche durch, die ein einzelnes falsches Paar-Ergebnis maskieren, selbst bei einer rückwärts sortierten 5-Elemente-Liste. Gelöst durch direktes Testen der internen Vergleichsfunktion, unter vollständiger Umgehung des unvorhersehbaren Verhaltens des fremden Sortieralgorithmus.

Fazit: Keine Zahl in diesem Bericht ist geschönt. Jede ist das Ergebnis iterativer, verifizierter Arbeit — einschließlich des Eingeständnisses und der Korrektur eigener Fehler unterwegs. Genau deshalb ist diesen Zahlen zu trauen: Die App verhält sich nicht nur bei den geprüften Beispielen korrekt — das Sicherheitsnetz der Tests drumherum erkennt künftige Regressionen tatsächlich, statt nur formal zu existieren.

Warum aus „2797 von 2797, 100%" kurzzeitig „3373 von 3374, 99,97%" wurde, bevor es wieder 100% waren: Nachdem zuerst 100% erreicht waren (oben), bekam der Interpreter neue Kursthemen dazu — direkte Iteration (for x in iterable: ohne range()), __str__/__eq__, @staticmethod/@classmethod, Dictionaries/Sets/Comprehensions/Generatoren — Hunderte neuer Zeilen, die noch nie durch Mutationstests gelaufen waren. Statt dieses Wachstum ungeprüft zu lassen (und stillschweigend weiter ein veraltetes „100%" anzuzeigen), wurde es genauso gründlich durch Stryker geschickt wie alles andere: 32 neue Regressionstests, jeder gegen einen konkreten überlebenden Mutanten geschrieben (erst den Code drumherum gelesen, logisch nachvollzogen, warum die bestehenden Tests ihn übersehen hatten, und erst dann den Test geschrieben) — keine Coverage-Auffüllung. Alles andere, was sich seit dem vorigen Mutationstest-Durchlauf (17. August) geändert hatte, wurde ebenfalls geprüft: der Parser (neue Grammatik für dieselben neuen Themen) und der Wiederholungsplaner (ein neuer Schwellenwert vor dem Interleaving) — beide bereits bei 100%, ohne weiteren Aufwand. Damit blieb genau ein Mutant ungelöst, und an dieser Stelle stand im Bericht kurzzeitig „3373 von 3374, 99,97%" — mit einer Erklärung, warum das ehrlicher ist als ein herbeigeführtes „100%": Eine Funktion zum Nachschlagen lokaler Variablen (Scope.tryGet) enthielt eine Prüfung, die theoretisch in zwei unterschiedlichen Szenarien relevant sein könnte, von denen sich eines angesichts jeder tatsächlichen Aufrufstelle in der App als mathematisch unmöglich erwies — und das andere, tatsächlich erreichbare Szenario war bereits durch einen eigenen Test abgedeckt. Beide Mutanten dieser Zeile mit einem einzigen Kommentar als „äquivalent" zu markieren, wäre der schnellste Weg zu „100%" gewesen — genau deshalb wurde es damals nicht getan: Das Werkzeug kann die Mutationsprüfung nur für eine ganze Zeile deaktivieren, nicht für einen einzelnen Mutanten, sodass diese Abkürzung auch die Abdeckung für das tatsächlich relevante Szenario weggewischt hätte.

Was danach geschah — und warum das echte 100% sind, kein Workaround: Statt sich mit 99,97% zufriedenzugeben oder den Rest still zu unterdrücken, lautete die nächste Frage, ob diese Zeile von vornherein toter Code war. Eine genauere Nachverfolgung jedes Aufrufs von tryGet() in der App (es gibt genau einen) und jeder Stelle, von der der von ihr geschützte Wert überhaupt stammen konnte, zeigte: ja — die Zeile war in jedem realen Programmzustand tatsächlich unerreichbar, nicht bloß schwer zu testen. Sie wurde vollständig entfernt. Das ist kein Trick: Das Löschen von wirklich totem Code kostet keine echte Abdeckung (anders als das Unterdrücken des Mutanten per Kommentar, das auch die echte, bereits verifizierte Abdeckung des Schwester-Mutanten weggeworfen hätte) — der Mutant, den diese Zeile erzeugte, hörte einfach zusammen mit ihr auf zu existieren. Die vollständige Testsuite (damals 1035 Tests) bestätigte, dass sich das Verhalten nicht im Geringsten änderte. Der nächste Stryker-Durchlauf ergab ein echtes 100,00%. Die Lehre aus diesem kleinen Umweg im Bericht selbst: Die Bereitschaft, eine ehrliche Zahl knapp unter 100% zu zeigen, erwies sich als der schnellere Weg zu einem echten 100% — nicht als langsamerer, als die Lücke von Anfang an zu übertünchen.

Woher das Wachstum von 3371 auf 3694 und von 9655 auf 9710 kommt (1. September 2026): Der Kurs ist gewachsen — ein neues 17. Modul, „Import, Module und Dateiverarbeitung", brachte echte (nicht nur syntaktisch erkannte) Ausführung von import/open()/with gegen ein simuliertes virtuelles Dateisystem, und ein neuer Abschnitt „Lifehacks" (15 „Vorher → Nachher"-Karten mit Erklärung, warum die kürzere Schreibweise dasselbe leistet). Beide Ergänzungen durchliefen dieselbe Disziplin wie der Rest der App: interpreter.ts und parser.ts wurden erneut durch Mutationstests geschickt (+238 bzw. +36 Mutanten, alle erkannt), der neue lifehacks-engine und manage-lifehacks wurden von null auf 100% gebracht (2 echte Testlücken geschlossen, nicht überdeckt), alle 15 Karten (30 Code-Schnappschüsse) wurden gegen CPython geprüft, ebenso die 2 neuen schleifenbasierten Übungen aus Modul 17. Am selben Tag wurde außerdem der letzte Übersetzungsrückstand geschlossen: Der Abschnitt „Lifehacks" und alle 434 neuen Schlüssel aus Modul 17 wurden sofort ins Spanische, Französische, Deutsche und Portugiesische übersetzt, sodass alle 6 Kurssprachen jetzt bei 100% Inhaltsparität liegen (12 214 Schlüssel pro Sprache) — daher stammt auch das Wachstum der Lokalisierungsvergleiche von 58 635 auf 61 070.

Anmerkungen

Während der iterativen Validierung wurden drei eng begrenzte Interpreter-Bugs gefunden und behoben — keiner hat je echte Kursinhalte betroffen, und alle drei sind jetzt dauerhaft durch Tests abgesichert, die verhindern, dass sie erneut auftreten:

Die maximale Intervallobergrenze des Wiederholungsplaners griff korrekt in mehreren Hundert dafür konzipierten Schritten, ohne eine einzige Ausnahme. Die obigen Ergebnisse stammen aus dem letzten, aktuellsten Validierungsdurchlauf, ohne bekannte offene Probleme.