Testen und Debugging von Algorithmen
In dieser praktischen Lerneinheit entwickelst du systematische Testfälle für einen Formular-Validierungsalgorithmus und lernst dabei wichtige Debugging-Techniken kennen. Du übst das methodische Aufspüren von Fehlern und erfährst, wie du Algorithmen durch gezieltes Testen robuster und zuverlässiger machst. Die erworbenen Fähigkeiten wendest du direkt an einem realitätsnahen Beispiel aus der Webentwicklung an.
Einführung
Zuverlässige Software entsteht nicht allein durch das Schreiben von Code, sondern durch systematisches Testen und gründliches Debugging.

Jeder Algorithmus – egal wie einfach er scheint – kann unerwartete Fehler enthalten, die seine Funktionalität beeinträchtigen.
In dieser Lerneinheit lernst du, wie du durch gezielte Testfallentwicklung, Verifikation und Debugging-Techniken sicherstellst, dass deine Algorithmen korrekt, stabil und nachvollziehbar arbeiten.
Lernziele
Nach dieser Lerneinheit kannst du:
- Testfälle gezielt entwickeln, um das Verhalten eines Algorithmus systematisch zu überprüfen und typische Fehlerquellen aufzudecken.
- Die Korrektheit eines Algorithmus verifizieren, sowohl mithilfe formaler Beweise als auch durch testbasierte Verfahren.
- Effektive Debugging-Strategien anwenden, um logische, syntaktische und Laufzeitfehler im Code effizient zu finden und zu beheben.
- Den gesamten Test- und Debuggingprozess praktisch umsetzen, indem du einen konkreten Algorithmus – wie die Zinsberechnung – planst, testest, überprüfst und optimierst.
Überleitung
In dieser Lerneinheit beschäftigen wir uns mit dem Entwickeln von Testfällen für einen Algorithmus, der Benutzereingaben in einem Formular validiert. Die Validierung von Benutzereingaben ist kritisch, um die Integrität und Sicherheit der Daten zu gewährleisten und Fehleingaben zu vermeiden.
Übung: Entwickle Testfälle für einen Algorithmus zur Validierung von Benutzereingaben in einem Formular
Schritt 1: Verstehen des zu testenden Algorithmus
Der Algorithmus, den wir validieren wollen, prüft, ob Eingaben in einem Formular den vorgegebenen Anforderungen entsprechen. Dazu gehören:
- Überprüfung auf leere Felder
- Validierung von E-Mail-Adressen
- Prüfung der Passwortstärke
- Altersüberprüfung
Testen und Debugging von Algorithmen
Schritt 2: Definition der Testfälle
Die Qualität unserer Tests hängt davon ab, wie gut und umfassend unsere Testfälle sind. Ein guter Testfall deckt sowohl normale als auch extreme Bedingungen ab und berücksichtigt mögliche Fehlerquellen.
Testfall 1: Überprüfung auf leere Felder
- Beschreibung: Eingabe ohne jegliche Zeichen im Formular
- Erwartetes Ergebnis: Fehlermeldung, dass das Feld nicht leer sein darf
Testfall 2: E-Mail-Validierung
- Gültige Eingabe:
nutzer@example.com - Ungültige Eingaben:
nutzer@example,@example.com,nutzer@@example.com - Erwartetes Ergebnis für ungültige Eingaben: Fehlermeldung, dass die E-Mail ungültig ist
Testfall 3: Passwortstärke
- Schwaches Passwort:
12345 - Starkes Passwort:
S1cher$P@ssw0rt! - Erwartetes Ergebnis für schwaches Passwort: Fehlermeldung über unzureichende Passwortstärke
Testfall 4: Altersüberprüfung
- Jüngeres Alter:
10 - Älteres Alter:
30 - Mindestalter:
18 - Erwartetes Ergebnis für jüngeres Alter: Fehlermeldung, dass das Mindestalter nicht erreicht ist
Testen und Debugging von Algorithmen
Schritt 3: Durchführung der Tests
Für das Durchführen von Tests kannst du entweder manuelle Verfahren wählen oder Automatisierungstools verwenden, wie:
def test_email_validator():
assert validate_email("nutzer@example.com") == True
assert validate_email("nutzer@example") == False
assert validate_email("@example.com") == False
assert validate_email("nutzer@@example.com") == FalseSchritt 4: Überprüfung der Ergebnisse
Vergleiche die Testergebnisse mit den erwarteten Ausgängen. Jede Abweichung muss untersucht werden, um festzustellen, ob es sich um einen Fehler im Algorithmus oder um einen Fehler im Testfall selbst handelt.
Testen und Debugging von Algorithmen
Schritt 5: Berichterstattung und Dokumentation
Dokumentiere die Testergebnisse gründlich und melde alle gefundenen Fehler. Eine gute Dokumentation ist unerlässlich, um zukünftige Tests und die Fehlerbehebung effektiv zu gestalten.
Verifikation der Korrektheit von Algorithmen mit Übungen
Die Verifikation der Korrektheit eines Algorithmus ist ein entscheidender Schritt in der Entwicklung von Software. Es geht darum, sicherzustellen, dass der Algorithmus für alle möglichen Eingaben die erwartete Ausgabe liefert. In dieser Lerneinheit behandeln wir verschiedene Techniken zur Verifikation und bieten praktische Übungen an, um das Gelernte zu festigen.
Formale Verifikation
Die formale Verifikation verwendet mathematische Methoden, um die Korrektheit eines Algorithmus bezüglich einer formalen Spezifikation zu beweisen. Dies erfolgt durch logische Ableitungen, die zeigen, dass unter allen möglichen Bedingungen der Algorithmus das gewünschte Verhalten zeigt.
Beispiel: Betrachten wir einen einfachen Algorithmus zur Bestimmung des Maximums in einer Liste von Zahlen.
def findMaximum(lst):
# Behandelt den Randfall (Edge Case) einer leeren Liste
if not lst:
return None
max_val = lst[0]
# Beginne die Schleife beim zweiten Element, da das erste bereits max_val ist
for num in lst[1:]:
if num > max_val:
max_val = num
return max_valDie formale Spezifikation könnte lauten: Für jede Eingabeliste lst muss die Funktion findMaximum(lst) das größte Element in lst zurückgeben, oder None, falls lst leer ist.
Beweis:
- Fall 1 (Leere Liste): Wenn
lstleer ist (if not lst), gibt der AlgorithmusNonezurück und erfüllt die Spezifikation für diesen Randfall. - Fall 2 (Nicht-leere Liste): Wenn
lstnicht leer ist, wirdmax_valmit dem ersten Element (lst[0]) initialisiert.- Die Schleife iteriert über alle verbleibenden Elemente (
lst[1:]). Für jedes Element wird geprüft, ob es größer alsmax_valist. Ist dies der Fall, wirdmax_valauf dieses größere Element aktualisiert. - Am Ende des Algorithmus hat
max_valden größten Wert inlst, da jeder Wert (ab dem zweiten) mit dem bisher größten Wert verglichen und dieser gegebenenfalls aktualisiert wurde.
- Die Schleife iteriert über alle verbleibenden Elemente (
Testbasierte Verifikation
Testbasierte Verifikation konzentriert sich auf das Testen des Algorithmus mit verschiedenen Eingaben, um sicherzustellen, dass die Ausgaben den Erwartungen entsprechen. Dieser Ansatz kann nicht die Korrektheit für alle möglichen Eingaben garantieren, aber er hilft, Fehler zu identifizieren und das Vertrauen in die Software zu erhöhen.
Übung:
Erstelle Tests für den findMaximum-Algorithmus. Bedenke dabei verschiedene Szenarien, wie leere Listen, Listen mit negativen Zahlen und Listen mit wiederholenden Zahlen.
Test für eine leere Liste:
assert findMaximum([]) == None # Prüft die korrekte Handhabung der leeren ListeTest für eine Liste mit negativen Zahlen:
assert findMaximum([-1, -5, -3]) == -1Test für eine Liste mit wiederholenden Zahlen:
assert findMaximum([2, 3, 2, 3, 3]) == 3Code-Review und Peer-Feedback
Eine weitere effektive Technik zur Verifikation ist das Code-Review durch Kollegen. Dabei untersuchen andere Entwickler den Code, um Fehler, Inkonsistenzen und Verbesserungsmöglichkeiten aufzuzeigen. Peer-Feedback kann Einsichten bieten, die beim selbstständigen Arbeiten möglicherweise übersehen werden.
Fazit
Die Verifikation der Korrektheit von Algorithmen ist ein kritischer Schritt in der Softwareentwicklung. Durch formale Verfahren, testbasierte Ansätze und Code-Reviews können Entwickler sicherstellen, dass ihr Code wie erwartet funktioniert und robust gegenüber verschiedenen Eingaben ist. Praktische Übungen helfen dabei, die theoretischen Konzepte zu verstehen und anzuwenden.
Strategien zur Fehlersuche und -behebung (Debugging) anhand praxisnaher Beispiele
Beim Entwickeln von Algorithmen ist das Debugging unerlässlich, um Fehler zu finden und zu beheben. Es gibt verschiedene Strategien, die dabei helfen können, diese Prozesse effektiver zu gestalten.
Print-Debugging
Eine der einfachsten und am weitesten verbreiteten Methoden ist das sogenannte Print-Debugging. Die Grundidee besteht darin, Ausgabeanweisungen (print in Python, System.out.println in Java, std::cout in C++ usw.) an kritischen Stellen im Code einzufügen, um den Wert von Variablen oder den Fluss des Programms während der Ausführung zu verfolgen.
def find_max(numbers):
max_num = None # Initialisierung mit None
for num in numbers:
# Diese Bedingung behandelt den Fall, dass max_num noch nicht gesetzt (None) ist
if max_num is None or num > max_num:
print(f"Aktualisiere max_num von {max_num} auf {num}")
max_num = num
return max_num
# Beispielnutzung
print(find_max([1, 3, 2]))In diesem Beispiel wird die print-Anweisung genutzt, um zu beobachten, wie sich max_num im Verlauf der Schleife ändert.
Verwendung von Debuggern
Viele moderne IDEs wie Visual Studio Code, Eclipse oder IntelliJ IDEA bieten integrierte Debugger, die ein weitaus mächtigeres Instrumentarium bieten als Print-Statements. Mit einem Debugger kannst du:
- Das Programm an Haltepunkten (Breakpoints) anhalten.
- Den Wert von Variablen inspizieren.
- Den Aufrufstack überprüfen.
- Das Programm schrittweise durchlaufen (Step-Over, Step-Into, Step-Out).
Beispiel in Visual Studio Code
sequenceDiagram participant U as Benutzer participant I as IDE participant P as Programm U->>I: Setzt Breakpoint loop Programmausführung I->>P: Erreicht Breakpoint P->>I: Hält Ausführung an I->>U: Zeigt aktuellen Zustand U->>I: Wählt "Step-Over"/"Step-Into" end
Durch den Einsatz von Debuggern kannst du den Ablauf deines Programms genau steuern und verstehen, wie es zu einem Fehler kommt.
Unit Tests
Unit Tests schreiben bedeutet, kleine Testfälle zu erstellen, die einzelne Teile des Codes isoliert prüfen. Dies hilft nicht nur dabei, Fehler frühzeitig zu entdecken, sondern auch sicherzustellen, dass frühere Teile des Codes nach Änderungen weiterhin korrekt funktionieren.
import unittest
def add(a, b):
return a + b # Simples Beispiel
class TestAddition(unittest.TestCase):
def test_addition_funktioniert(self):
self.assertEqual(add(2, 3), 5)
def test_addition_negativ(self):
self.assertEqual(add(-2, -3), -5)
if __name__ == '__main__':
unittest.main()Mit Hilfe von Frameworks wie JUnit (Java) oder pytest (Python) können diese Tests automatisiert und regelmäßig ausgeführt werden, um die Gesundheit des Codes zu überwachen.
Logging
Logging ermöglicht es, Informationen über den Ablauf eines Programms dauerhaft festzuhalten. Im Unterschied zu Print-Anweisungen sind Log-Ausgaben stärker konfigurierbar und können in verschiedenen Detaillierungsgraden und an verschiedenen Orten (z.B. Konsole, Dateien) ausgegeben werden.
Python:
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def risky_operation():
try:
# Riskante Operation
logger.info("Führe riskante Operation aus")
except Exception as e:
logger.error(f"Ein Fehler ist aufgetreten: {e}")
risky_operation()Mit einer solchen Struktur kannst du flexibel entscheiden, welche Informationen von Interesse sind und wie sie präsentiert werden sollen.
Diese Methoden zum Debugging in realen Szenarien helfen, Fehler effizient zu identifizieren und die Qualität der Software nachhaltig zu sichern.
Testen und Debuggen eines Algorithmus zur Berechnung von Zinsen in einem Bankensystem
Beim Testen und Debuggen eines Zinsberechnungsalgorithmus in einem Bankensystem ist es entscheidend, Präzision und Korrektheit sicherzustellen. Zinssätze beeinflussen direkt finanzielle Entscheidungen von Banken und deren Kunden. Ein falsch implementierter Algorithmus kann zu finanziellen Verlusten oder regulatorischen Problemen führen.
Zinsberechnung verstehen
Zuerst muss man die Formel für die Zinsberechnung verstehen. Für einfache Zinsen lautet sie:
- steht für die Zinsen.
- ist das Kapital (Hauptsumme).
- ist der Zinssatz (pro Jahr).
- ist die Zeit in Jahren.
Für die Berechnung von Zinseszinsen verwendet man:
Zinsberechnungsalgorithmus implementieren
Jetzt implementieren wir einen Basisalgorithmus in Python, der einfache Zinsen berechnet:
def berechne_einfache_zinsen(kapital, zinssatz, zeit):
return kapital * zinssatz * zeitUnd ein Algorithmus für Zinseszinsen:
def berechne_zinseszinsen(kapital, zinssatz, zeit):
return kapital * ((1 + zinssatz) ** zeit) - kapitalTestfälle entwickeln
Zur Verifikation der Korrektheit unseres Algorithmus entwickeln wir einige Testfälle:
- Einfache Zinsen für ein Kapital von 1000€ bei einem Zinssatz von 5% über 1 Jahr.
- Zinseszinsen für das gleiche Kapital und Zinssatz über 5 Jahre.
- Negativtest: Überprüfe das Verhalten des Algorithmus bei negativen Werten für Zeit.
Testen des Algorithmus
Jetzt führen wir die Tests aus und überprüfen, ob die Ergebnisse den Erwartungen entsprechen:
print(berechne_einfache_zinsen(1000, 0.05, 1)) # Erwartet: 50.0
print(berechne_zinseszinsen(1000, 0.05, 5)) # Erwartet: 276.2815625Debugging
Wenn die Ergebnisse nicht den Erwartungen entsprechen, beginnt die Fehlersuche. Überprüfe den Algorithmus auf:
- Rechenfehler: Stelle sicher, dass die mathematische Logik korrekt implementiert ist.
- Datentypen: Überprüfe, ob die Variablen den richtigen Typ haben.
- Logikfehler: Stelle sicher, dass die Schritte in der richtigen Reihenfolge ausgeführt werden.
Zur Isolierung des Fehlers kann man strategisch print()-Statements einsetzen oder einen Debugger verwenden, um Variablenwerte zu inspizieren und den Ablauf Schritt für Schritt durchzugehen.
Zusammenfassung
Zusammenfassung:
In dieser Lerneinheit lernst du, wie man Algorithmen systematisch testet, ihre Korrektheit verifiziert und auftretende Fehler effizient behebt. Ziel ist es, sichere, fehlerfreie und nachvollziehbare Programme zu entwickeln.
1. Testfallentwicklung
Beim Testen von Algorithmen werden gezielt Testfälle erstellt, um das Verhalten des Programms unter verschiedenen Bedingungen zu überprüfen. Ein Testfall beschreibt:
- Eingabe (was getestet wird)
- Erwartete Ausgabe (was das Programm liefern soll)
- Tatsächliche Ausgabe (was es tatsächlich liefert)
Gute Testfälle decken sowohl Standardfälle als auch Randfälle ab – also Situationen, in denen fehlerhafte Eingaben oder extreme Werte vorkommen. Beispiel: Validierung von Benutzereingaben in Formularen (leere Felder, ungültige E-Mail, schwaches Passwort, Altersgrenze).
2. Verifikation der Korrektheit
Die Verifikation prüft, ob ein Algorithmus logisch korrekt ist, d. h. ob er für alle möglichen Eingaben das erwartete Ergebnis liefert.
- Formale Verifikation nutzt mathematische Beweise, um die Korrektheit zu zeigen (z. B. Beweis, dass
findMaximum()immer das größte Element findet). - Testbasierte Verifikation prüft ausgewählte Eingaben durch praktische Tests.
- Peer-Review ergänzt die Überprüfung durch Kolleginnen und Kollegen, die Lesbarkeit, Effizienz und Sonderfälle bewerten.
3. Debugging-Strategien
Beim Debugging geht es darum, Fehler zu identifizieren und zu beheben. Wichtige Strategien sind:
- Print-Debugging: Ausgabe von Zwischenergebnissen im Code, um Fehlerquellen nachzuvollziehen.
- Debugger in IDEs: Breakpoints setzen, Variablen prüfen und Programmabläufe Schritt für Schritt analysieren.
- Unit Tests: Kleine Tests, die gezielt einzelne Funktionen prüfen, um Regressionen zu vermeiden.
- Logging: Systematische Aufzeichnung von Ereignissen zur Nachverfolgung von Programmzuständen.
4. Anwendung: Zinsberechnungsalgorithmus
Am Beispiel eines Zinsberechnungsalgorithmus wird der gesamte Prozess praktisch angewendet. Formeln:
- Einfache Zinsen: Z = K · p · t
- Zinseszinsen: Z = K · (1 + p)ᵗ − K
Nach der Implementierung werden Testfälle erstellt (z. B. für verschiedene Zinssätze oder Zeiträume), ausgeführt und anschließend mit Debugging-Methoden überprüft, falls Abweichungen auftreten.
5. Fazit
Das Testen, Verifizieren und Debuggen sind wesentliche Bestandteile der Softwareentwicklung. Sie gewährleisten:
- Zuverlässigkeit: Der Algorithmus funktioniert korrekt.
- Nachvollziehbarkeit: Fehler können systematisch erkannt werden.
- Qualitätssicherung: Software bleibt auch bei Änderungen stabil.
Eine gründliche Kombination aus Tests, Verifikation und Debugging führt zu robustem, wartbarem Code und stärkt das Verständnis für algorithmisches Denken.