Unit Tests schreiben
Einrichten der Testumgebung
Statt der bereits angelegten Datei src/vs.policy/vs/policy/tests.py erstellen wir ein eigenes tests-Modul:
$ rm -rf src/vs.policy/vs/policy/tests.py $ mkdir src/vs.policy/vs/policy/tests $ touch src/vs.policy/vs/policy/tests/__init__.py
Anschließend definieren wir im neu erstellten tests-Ordner zunächst ein Test-Fixture, eine gleichbleibende Testumgebung mit der Basisklasse VsPolicyTestCase. Hierzu erstellen wir im tests-Verzeichnis die Datei base.py mit folgendem Inhalt:
from Products.Five import zcml
from Products.Five import fiveconfigure
from Testing import ZopeTestCase as ztc
from Products.PloneTestCase import PloneTestCase as ptc
from Products.PloneTestCase.layer import onsetup
@onsetup
def setup_vs_policy():
fiveconfigure.debug_mode = True
import vs.policy
zcml.load_config('configure.zcml', vs.policy)
fiveconfigure.debug_mode = False
ztc.installPackage('vs.policy')
setup_vs_policy()
ptc.setupPloneSite(products=['vs.policy'])
class VsPolicyTestCase(ptc.PloneTestCase):
"""This base class is used for all tests in this package.
Utility or setup code can be added if necessary.
"""
Der @onsetup-Decorator sorgt dafür, dass zunächst vs.policy importiert und erst dann eine Plone-Site mit vs.policy aufgesetzt wird. Darüberhinaus wird die configure.zcml-Datei auch dann registriert, wenn kein entsprechender zcml-slug für die Site vorhanden ist.
VsPolicyTestCase ist die Basisklasse für alle Tests in diesem Paket. Bei Bedarf kann hier weiterer Code für das Setup oder Hilfsmethoden eingefügt werden.
Anmerkung: ZopeTestCase konfiguriert Zope, jedoch ohne automatisch die Produkte mitzuinstallieren. Dies kann explizit angegeben werden, z.B. mit:
ztc.installProduct('EasyNewsletter')
installProduct ist ein verzögerter Aufruf, der nicht innerhalb von setup_registration aufgerufen werden kann, da er dort zu spät ausgeführt wird. Daher sollte installProduct außerhalb der setup_registration-Funktion angegeben werden. Dieses Problem tritt auf, seitdem mit Plone 4 das five-Package nicht mehr im Products-Namensraum ist.
Eine vollständige Basisklasse finden Sie im EasyNewsletter: base.py.
Unit Tests schreiben
Die eigentlichen Unit-Tests werden in der Datei test_setup.py definiert und zu einer Testsuite zusammengestellt:
import unittest
from vs.policy.tests.base import VsPolicyTestCase
from Products.CMFCore.utils import getToolByName
class TestSetup(VsPolicyTestCase):
def test_portal_title(self):
self.assertEquals("Veit Schiele", self.portal.getProperty('title'))
def test_portal_description(self):
self.assertEquals("Welcome to Veit Schiele", self.portal.getProperty('description'))
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestSetup))
return suite
Unit Tests, die auf dem Python unittest-Modul, ZopeTestCase und PloneTestCase basieren, müssen sich an einige Namenskonventionen halten:
- Alle Testdateien müssen mit test beginnen, z.B. test_setup.py.
- In den Testdateien werden Klassen für Testfälle definiert, die ein oder mehrere Testmethoden enthalten können, die ebenfalls mit test beginnen müssen, z.B. test_portal_title.
- Zunächst wird die Basisklasse importiert, dann die Klassen für die Testfälle und schließlich die Test Suite selbst definiert.
- Jede Testsuite kann aus mehreren Testklassen bestehen. Wird die Testsuite ausgeführt, werden alle Testmethoden aller Testklassen der Test-Suite ausgeführt.
- Innerhalb einer Testklasse kann die afterSetUp()-Methode unmittelbar vor jedem Test aufgerufen werden um Testdaten für diesen Test anzugeben. Nachdem der Test durchgeführt wurde, werden die Transaktionen zurückgenommen, so dass normalerweise keine Artefakte zurückbleiben.
- Werden jedoch Änderungen außerhalb von Zope vorgenommen, müssen diese mit der Methode beforeTearDown() aufgeräumt werden.
- Die in einer Testklasse verwendeten Methoden wie self.assertEqual() oder self.failUnless() sind Assertion-Methoden, und wenn eine von ihnen fehlschlägt, gilt der ganze Test als fehlgeschlagen.
Assertion und Hilfsmethoden
Assertion-Methoden überprüfen, ob etwas wahr oder falsch ist. Daher kann aus den Tests auch herausgelesen werden, wie sich Ihr Produkt verhalten soll, welche Fähigkeiten es enthält. Die Liste der Assertion-Methoden ist ausführlich in der Python-Dokumentation für unittest.TestCaseObjects enthalten. Die häufigsten sind:
- failUnless(expr)
- stellt sicher, dass der Ausdruck expr wahr ist.
- assertEqual(expr1, expr2)
- stellt sicher,dass expr1 gleich expr2 ist.
- assertRaises(exception, callable, ...)
stellt sicher, dass beim Aufruf von callable die Fehlermeldung exception ausgegeben wird.
Hinweis: callable sollte der Name einer Methode oder ein aufrufbares Objekt sein, nicht ein aktueller Aufruf, z.B.:
self.assertRaises(AttributeError, myObject.myMethod, someParameter)
- fail()
- Dies ist sinnvoll, wenn ein Test noch nicht fertiggestellt ist oder in einem if-Statement, das deutlich macht, dass der Test fehlgeschlagen ist.
ZopeTestCase und PloneTestCase fügen zu den Assertion-Methoden noch weitere hilfreiche Methoden und Variablen hinzu, die mit Zope interagieren. Hier nur kurz die wesentlichen Variablen:
- self.portal
- Die PloneSite, in der der Test ausgeführt wird.
- self.folder
- Der member-Ordner des Mitglieds, als der die Tests ausgeführt werden.
Und hier die wesentlichen Methoden:
- self.logout()
- abmelden, d.i. die Rolle anonymous bekommen;
- self.login()
- sich erneut anmelden; wird ein Nutzername mit übergeben, erfolgt die Anmeldung als dieser Nutzer.
- self.setRoles(roles)
durchläuft eine Liste von Rollen, die angenommen werden sollen.
self.setRoles((Manager,)) lässt Sie beispielsweise die Rolle des Managers für eine bestimmte Zeit annehmen.
- self.setPermissions(permissions)
- analog können auch Berechtigungen für den Testnutzer in self.folder angegeben werden;
- self.setGroups(groups)
- eine Liste von Gruppen, der der aktuelle Nutzer angehören soll.
Mehr über Unit Tests in Python erfahren Sie in der unittest-Python-Dokumentation.
Tipps & Tricks
Übernehmen Sie Tests z.B. aus Plone wenn diese Ihren eigenen Absichten entsprechen.
Dummy-Implementierungen sind häufig der einzige Weg um bestimmte Funktionen zu testen. Siehe auch CMFPlone/tests/dummy.py für einige Dummy-Objekt-Beispiele.
Tests können auch verwendet werden um Dinge auszuprobieren – sie sind eine sichere Umgebung.
Während des Debugging können print-Statements in den Test eingefügt werden um nachvollziehbare Hinweise im Terminal zu erhalten.
Es kann jedoch auch gleich der Python-Debugger in die Testmethoden importiert werden mit:
import pdb; pdb.set_trace()
Anschließend können Sie mit r schrittweise durch den Testkode gehen.
Mehr zum Python-Debugger erfahren Sie in Debugging und in der Python-Dokumentation.
