Dokumentation und Tests Pythonischen Codes mit Doctest
Daniel Hayes
Full-Stack Engineer · Leapcell

Einführung
In der schnelllebigen Welt der Softwareentwicklung sind die Sicherstellung der Codequalität und die Pflege aktueller Dokumentation von größter Bedeutung. Oft werden diese beiden entscheidenden Aspekte als getrennte und manchmal sogar konkurrierende Anliegen behandelt. Entwickler schreiben möglicherweise umfangreiche Testsuiten, die vom Code getrennt sind, und erstellen dann eine Dokumentation, die ständige Synchronisation erfordert. Dieser traditionelle Ansatz kann dazu führen, dass die Dokumentation veraltet und die Tests mit der Weiterentwicklung der Codebasis weniger effektiv werden.
Stellen Sie sich eine Welt vor, in der Ihre Dokumentation Ihr Test ist. Eine Welt, in der den Benutzern gezeigte Beispiele automatisch auf Korrektheit überprüft werden und in der der Akt der Dokumentation Ihres Codes seine Zuverlässigkeit inhärent stärkt. Dies ist kein unerfüllbarer Traum; es ist eine Realität, die in Python dank seines leistungsstarken doctest
-Moduls möglich ist. Indem Testfälle nahtlos direkt in Docstrings integriert werden, bietet doctest
eine einzigartige und äußerst effektive Methode, um selbstdokumentierenden und selbsttestenden Code zu erstellen. Dieser Artikel befasst sich mit den Prinzipien, der Implementierung und den praktischen Vorteilen der Verwendung von doctest
zur Verbesserung Ihres Python-Entwicklungs-Workflows.
Die Synergie von Dokumentation und Tests
Bevor wir uns mit den Besonderheiten von doctest
befassen, lassen Sie uns einige Kernkonzepte klären, die seiner Philosophie zugrunde liegen.
-
Docstrings: In Python ist ein Docstring eine Zeichenkette, die als erste Anweisung in der Definition eines Moduls, einer Funktion, einer Klasse oder einer Methode vorkommt. Er dient als Inline-Dokumentation und erklärt, was der Code tut, welche Argumente er hat und was er zurückgibt. Sie sind über das
__doc__
-Attribut zugänglich und werden häufig von Tools wie Sphinx zur Generierung von API-Dokumentationen verwendet. -
Testgetriebene Entwicklung (TDD): Ein Softwareentwicklungsprozess, bei dem Tests vor dem eigentlichen Code geschrieben werden. Dieser Ansatz stellt sicher, dass der Code die spezifizierten Anforderungen erfüllt und hilft bei der Gestaltung robuster, modularer Komponenten.
-
Unit-Tests: Die Praxis, einzelne Einheiten oder Komponenten einer Softwareanwendung isoliert zu testen. Ziel ist es, zu validieren, dass jede Einheit der Software wie vorgesehen funktioniert.
doctest
schließt die Lücke zwischen Docstrings und Unit-Tests, indem es Ihnen ermöglicht, Beispielverwendungen zusammen mit ihren erwarteten Ausgaben direkt in Ihren Funktions- oder Modul-Docstrings einzubetten. Diese Beispiele dienen dann als ausführbare Tests.
Wie Doctest funktioniert
Das doctest
-Modul sucht nach Texten, die in Docstrings wie interaktive Python-Sitzungen aussehen. Insbesondere sucht es nach Zeilen, die mit >>>
(der Eingabeaufforderung der interaktiven Python-Sitzung) beginnen, und behandelt die nachfolgenden Zeilen als erwartete Ausgaben für diesen interaktiven Befehl.
Wenn doctest
ausgeführt wird, führt es den Code nach der Eingabeaufforderung >>>
aus und vergleicht die tatsächliche Ausgabe mit der erwarteten Ausgabe im Docstring. Stimmen sie überein, ist der Test erfolgreich; andernfalls schlägt er fehl.
Lassen Sie uns dies anhand eines einfachen Beispiels veranschaulichen:
def add(a, b): """ Hinzufügen zweier Zahlen und Rückgabe ihrer Summe. >>> add(2, 3) 5 >>> add(10, -5) 5 >>> add(0, 0) 0 """ return a + b
In dieser add
-Funktion enthält der Docstring drei verschiedene Testfälle. Jeder Fall zeigt, wie add
aufgerufen werden sollte und welchen Rückgabewert es haben sollte.
Um diese Tests auszuführen, können Sie am Ende Ihres Skripts einen einfachen Block hinzufügen:
import doctest def add(a, b): """ Hinzufügen zweier Zahlen und Rückgabe ihrer Summe. >>> add(2, 3) 5 >>> add(10, -5) 5 >>> add(0, 0) 0 """ return a + b if __name__ == "__main__": doctest.testmod()
Wenn Sie dieses Skript ausführen: python your_module.py
, findet und führt doctest.testmod()
automatisch alle Doctests im aktuellen Modul aus. Wenn alle Tests erfolgreich sind, sieht die Ausgabe ungefähr so aus:
$ python your_module.py
(Keine Ausgabe bedeutet standardmäßig Erfolg)
Wenn ein Test fehlschlägt, meldet doctest
die Diskrepanz:
Lassen Sie uns absichtlich einen Fehler einfügen:
def add(a, b): """ Hinzufügen zweier Zahlen und Rückgabe ihrer Summe. >>> add(2, 3) 6 """ return a + b if __name__ == "__main__": doctest.testmod()
Die Ausführung würde Folgendes ergeben:
$ python your_module.py
**********************************************************************
Datei "your_module.py", Zeile 5, in __main__.add
Fehlgeschlagenes Beispiel:
add(2, 3)
Erwartet:
6
Erhalten:
5
**********************************************************************
1 Element hatte Fehler:
1 von 1 in __main__.add
***Test fehlgeschlagen*** 1 Fehler.
Erweiterte Funktionen und Direktiven
doctest
bietet mehr als nur einfachen Abgleich von Eingabeaufforderung und Ausgabe. Sie können spezielle Direktiven verwenden, um Situationen wie Ausnahmen, nicht geordnete Ausgaben oder Gleitkommavergleiche zu behandeln.
-
ELLIPSIS
: Nützlich, wenn Sie erwarten, dass Teile der Ausgabe variieren (z. B. Speicheradressen oder Objekt-IDs). Verwenden Sie...
in Ihrer erwarteten Ausgabe, um ein beliebiges Unterzeichenfolgen abzugleichen.def get_object_info(obj): """
Gibt eine String-Darstellung des Typs und der ID eines Objekts zurück.
>>> get_object_info(1)
"<class 'int'> at 0x..."
"""
return f"{type(obj)} at {hex(id(obj))}"
```
Um dies zu aktivieren, müssen Sie `optionflags=doctest.ELLIPSIS` an `doctest.testmod()` übergeben.
-
Erwartete Ausnahmen: Wenn von einem Testfall erwartet wird, eine Ausnahme auszulösen, können Sie dies direkt angeben.
def divide(a, b): """ Teilt zwei Zahlen. >>> divide(10, 2) 5.0 >>> divide(10, 0) Traceback (most recent call last): ... ZeroDivisionError: division by zero """ return a / b
-
NORMALIZE_WHITESPACE
: Ignoriert Unterschiede im Leerraum (Leerzeichen, Tabulatoren, Zeilenumbrüche) beim Vergleichen von erwarteter und tatsächlicher Ausgabe.def format_list(items): """ Formatiert eine Liste von Elementen in einen String. >>> format_list(['apple', 'banana', 'cherry']) # doctest: +NORMALIZE_WHITESPACE "Items: - apple - banana - cherry" """ return "Items:\n" + "\n".join(f"- {item}" for item in items)
Sie können diese Flags global für testmod()
aktivieren oder lokal mit doctest: +FLAG
.
Anwendungsszenarien
doctest
glänzt in mehreren Szenarien:
- Beispiele in der Dokumentation: Der primäre Anwendungsfall. Es stellt sicher, dass jede Beispielverwendung, die Benutzern oder Kollegenentwicklern in Ihren Docstrings angezeigt wird, immer korrekt und aktuell ist. Dies schafft Vertrauen in Ihre Dokumentation.
- Einfache Funktions tests: Für kleine, gut definierte Funktionen kann
doctest
ein schneller und effektiver Weg sein, ihr erwartetes Verhalten zu erfassen, ohne eine separateunittest
- oderpytest
-Suite einrichten zu müssen. - Lehrmaterial: Beim Verfassen von Tutorials oder beim Unterrichten von Python stellt
doctest
sicher, dass für Studenten bereitgestellte Codeausschnitte ausführbar sind und die beworbenen Ergebnisse liefern. - Schnelle Regressionsprüfungen: Wenn ein Fehler behoben wurde, kann ein neuer
doctest
zum Docstring der entsprechenden Funktion hinzugefügt werden, um zu verhindern, dass der Fehler erneut auftritt, und dient somit als einfacher, aber effektiver Regressionstest.
Best Practices und Einschränkungen
Obwohl leistungsstark, ist doctest
kein Ersatz für umfassende Unit-Test-Frameworks wie unittest
oder pytest
.
Wann doctest
zu verwenden ist:
- Für einfache, anschauliche Beispiele.
- Zum Testen der öffentlichen API von Funktionen, Methoden und Klassen.
- Wenn der Testfall natürlich in ein ausführbares Beispiel passt.
Einschränkungen und wann andere Frameworks zu verwenden sind:
- Komplexe Einrichtung:
doctest
ist nicht ideal für Tests, die eine umfangreiche Einrichtung erfordern (z. B. Mocking von Datenbanken, externen Diensten oder komplexen Objektgraphen). - Fixture-Verwaltung: Andere Frameworks bieten eine robuste Fixture-Verwaltung zur Initialisierung von Testumgebungen.
- Parametrisierte Tests: Das Ausführen derselben Testlogik mit unterschiedlichen Eingaben ist mit der Parametrisierung von
pytest
einfacher. - Leistungstests:
doctest
ist nicht für Leistung oder Stresstests konzipiert. - Fehlerberichterstattung: Obwohl
doctest
Fehler meldet, sind die detaillierten Berichterstattungsfunktionen vonpytest
oderunittest
für größere Projekte oft überlegen.
Ein gängiger Ansatz ist die Verwendung von doctest
für klare, öffentlich sichtbare Beispiele und die Verwendung von pytest
oder unittest
für interne, komplexe oder Grenzfalltests.
Fazit
doctest
verkörpert die Python-Philosophie "Batteries included" (alles dabei), indem es eine leichte, aber effektive Methode zur Verschmelzung von Dokumentation und Tests bietet. Durch das Einbetten ausführbarer Beispiele direkt in Ihre Docstrings erstellen Sie Code, der inhärent selbstvalidierend und leichter zu verstehen ist. Dieser einzigartige Ansatz steigert nicht nur die Codequalität, indem er Regressionen frühzeitig erkennt, sondern verbessert auch die Wartbarkeit dramatisch, indem er sicherstellt, dass Ihre Dokumentation stets das tatsächliche Verhalten Ihres Codes widerspiegelt. Nutzen Sie doctest
, und lassen Sie Ihre Dokumentation zum Wächter der Korrektheit Ihres Codes werden.