DocTests
DocTests bieten eine interessante Möglichkeit, um Python-Code zu testen indem die Geschichte einer Methode oder Klasse erzählt wird. In Zope 3 sind DocTests weit verbreitet und werden oft für Unit Tests verwendet. Zudem sind DocTests oft eine gute Möglichkeit, gleichzeitig Dokumentationen und Tests zu schreiben.
Meist werden DocTests in eine einzige Datei geschrieben. Dabei ist jedoch zu beachten, dass nicht strikt zwischen den einzelnen Schritten getrennt wird und daher keine sauberen Unit Tests durchgeführt werden. Alternativ kann auch für jede Methode oder Klasse ein DocTest in deren docstring geschrieben werden. Die Syntax der DocTests ist zwar identisch, aber jeder docstring wird als eigene Test Fixture durchgeführt, die eine saubere Trennung der Tests erlaubt.
Hier nun ein einfaches Beispiel aus /Products/CMFPlone/PloneTool.py:
def normalizeString(self, text, relaxed=False):
"""Normalizes a title to an id.
normalizeString() converts a whole string to a normalized form that
should be safe to use as in a url, as a css id, etc.
If relaxed=True, only those characters that are illegal as URLs and
leading or trailing whitespace is stripped.
>>> ptool = self.portal.plone_utils
>>> ptool.normalizeString("Foo bar")
'foo-bar'
…
"""
return utils.normalizeString(text, context=self, relaxed=relaxed)
Dabei ist zu beachten, dass der docstring der Methode sowohl den einfachen Text enthält, der beschreibt, was die Methode tut, als auch Python-Statements, wie man sie aus einem interaktiven Python-Interpreter kennt.
Und tatsächlich führt der DocTest Runner jede Zeile, die mit >>> beginnt, im Python-Interpreter aus. Folgt diesem Statement eine Zeile, die genausoweit eingerückt ist, nicht leer ist und nicht mit >>> beginnt, so wird dies als Ergebnis des Statements erwartet. Stimmen sie nicht überein, gibt doctest eine Fehlermeldung aus.
- Hinweis 1
- Wird kein Ausgabewert angegeben, wird von der Methode keine Ausgabe erwartet. Gibt die Methode dennoch etwas aus, wirft doctest eine Fehlermeldung aus.
- Hinweis 2
- ... bedeutet »beliebig viele Zeichen«. Dies ist sinnvoll für Werte, die nicht vorhergesagt werden können wie automatisch generierte IDs, die auf dem aktuellen Datum oder zufälligen Zahlen beruhen.
Testen mit DocTest
DocTests sind ein Feature von Python 2 und daher kann Python’s unittest-Library für einfache Doctests verwendet werden. Zope 3 erweitert die Funktionalität noch um das zope.testing-Modul:
import unittest
from zope.testing import doctest
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite('README.txt'),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Wird die Datei z.B. als test_doctests.py im tests-Verzeichnis Ihres Produkts gespeichert, kann es mit den üblichen Aufrufen gestartet werden. Wenn Sie sich Zope3-DocTests anschauen, können Sie häufig feststellen, dass in den ersten Zeilen die Komponentenarchitektur oder einzelne Komponenten explizit geladen werden.
Um die Testumgebung für DocTests anzugeben, können Sie z.B. folgendes eingeben:
import unittest
import doctest
from zope.testing import doctestunit
from zope.component import testing, eventtesting
from Testing import ZopeTestCase as ztc
from vs.registration.tests import base
def test_suite():
return unittest.TestSuite([
# Demonstrate the main content types
ztc.ZopeDocFileSuite(
'README.txt', package='vs.registration',
test_class=base.RegistrationFunctionalTestCase,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE | doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS),
])
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Dieser Test ist aus vs.registration/tests/test_doctest.py entnommen. Er führt dabei die README.txt-Datei aus vs.registration/vs/registration/ aus.
- Hinweis 3
Mit diesem Setup referenziert die Variable self auf eine PloneTestCase-Instanz. Demzufolge können Sie z.B. folgendes angeben:
>>> self.portal.invokeFactory('Registration', 'my-first-registration')um ein Dokument im Wurzelverzeichnis Ihrer Site anzulegen. Auch alle anderen Methoden aus PloneTestCase und ZopeTestCase sollten innerhalb von DocTests laufen.
- Hinweis 4
In optionflags lassen sich Optionen und Anweisungen für docteststs angeben, u.a.:
- doctest.NORMALIZE_WHITESPACE
- Wenn angegeben, werden alle Abfolgen von Leerzeichen und/oder Zeilenumbrüchen als gleich betrachtet.
- doctest.ELLIPSIS
- Wenn angegeben, kann eine Ellipse ... in der erwarteten Ausgabe auf jede beliebige Zeichenfolge passen.
- doctest.REPORT_ONLY_FIRST_FAILURE
- Wenn angegeben, wird der erste fehlgeschlagene Test angezeigt, nicht jedoch der Ausgang der weiteren Tests. Hiermit wird verhindert, dass doctest fehlgeschlagene Tests aufgrund von vorangehend gescheiterten Tests ausgibt.
Eine vollständige Übersicht über alle Optionen finden Sie in Option Flags and Directives
DocTest Tipps & Tricks
- Dokumentation von DocTest lesen
- Das DocTest-Modul kommt mit einer umfangreichen Dokumentation.
- Ein Test ist eine Reihe von Python-Statements.
Sie können z.B. auf Hilfsmethoden in Ihrem Produkt verweisen, angenommen Ihr Produkt enthält die Methode reset(self) in my.package.tests.utils, so kann diese Methode mit DocTest aufgerufen werden:
>>> from my.package.tests.utils import reset >>> reset()
- Die Testsuite kann zusätzliche Funktionen einführen
- Möchten Sie z.B. ein Produkt in obigem Beispiel verfügbar machen, müssen Sie nur ZopeTestCase.installProduct() in der testsuit-Datei Ihres Produkts aufrufen.
- Debugging
Wenn Sie import pdb; pdb.set_trace() in Ihren DocTest einfügen, können Sie zwar nicht schrittweise durch Ihren Kode gehen, aber Variablen und der Status der Test Fixture kann mit print ausgegeben werden.
Weitere Informationen zum Debugging erhalten Sie in der Python Dokumentation.
- Exceptions (Ausnahmen) ausgeben
Folgender Code gibt Exceptions aus:
>>> try: ... someOperation() ... except: ... import pdb; pdb.set_trace() >>> # continue as normal
