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 TestCase
, die an den Layer VS_POLICY_INTEGRATION
gebunden wird. Hierzu erstellen wir im tests
-Verzeichnis die Datei base.py
mit folgendem Inhalt:
import unittest2 as unittest
from plone.testing import z2
from plone.app.testing import TEST_USER_NAME
from plone.app.testing import TEST_USER_PASSWORD
from vs.policy.tests import layer
def get_browser(app, loggedIn=True):
browser = z2.Browser(app)
if loggedIn:
auth = 'Basic %s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD)
browser.addHeader('Authorization', auth)
return browser
class TestCase(unittest.TestCase):
layer = layer.VS_POLICY_INTEGRATION
class FunctionalTestCase(unittest.TestCase):
layer = layer.VS_POLICY_FUNCTIONAL
In layer.py
werden anschließend die Test-Layer VS_POLICY_INTEGRATION
und VS_POLICY_FUNCTIONAL
definiert, die beide auf VS_POLICY_LAYER
basieren:
from plone.app.testing import applyProfile
from plone.app.testing import PloneFixture
from plone.app.testing import PloneSandboxLayer
from plone.app.testing import PloneTestLifecycle
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
from plone.testing import z2
from zope.configuration import xmlconfig
class VsPolicyFixture(PloneFixture):
# No sunburst please
extensionProfiles = ()
VS_POLICY_FIXTURE = VsPolicyFixture()
class VsPolicyTestLifecycle(PloneTestLifecycle):
defaultBases = (VS_POLICY_FIXTURE, )
class IntegrationTesting(VsPolicyTestLifecycle, z2.IntegrationTesting):
pass
class FunctionalTesting(VsPolicyTestLifecycle, z2.FunctionalTesting):
pass
class VsPolicyLayer(PloneSandboxLayer):
defaultBases = (VS_POLICY_FIXTURE, )
def setUpZope(self, app, configurationContext):
import vs.policy
xmlconfig.file("configure.zcml", vs.policy,
context=configurationContext)
z2.installProduct(app, 'vs.policy')
def tearDownZope(self, app):
z2.uninstallProduct(app, 'vs.policy')
def setUpPloneSite(self, portal):
applyProfile(portal, 'vs.policy:default')
setRoles(portal, TEST_USER_ID, ['Manager'])
portal.invokeFactory('Folder', 'test-folder')
setRoles(portal, TEST_USER_ID, ['Member'])
VS_POLICY_LAYER = VsPolicyLayer()
VS_POLICY_INTEGRATION = IntegrationTesting(
bases=(VS_POLICY_LAYER, ), name="VsPolicyLayer:Integration")
VS_POLICY_FUNCTIONAL = FunctionalTesting(
bases=(VS_POLICY_LAYER, ), name="VsPolicyLayer:Functional")
Die eigentlichen Tests werden in der Datei test_test.py
definiert:
from vs.policy.tests.base import FunctionalTestCase
class TestTest(FunctionalTestCase):
def test_test(self):
self.assertTrue(True)
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.
Testmethoden ü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 Testmethoden 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 Hilfsmethoden:
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.
Der Testrunner kann nun gestartet werden mit:
$ ./bin/test -s vs.policy
Wären die Tests geschrieben worden, bevor die Profile erstellt wurden, hätten beide Tests fehlschlagen müssen und der Testrunner folgendes ausgeben:
AssertionError:"Welcome to Veit Schiele != ''
…
AssertionError:'Veit Schiele != 'Plone site'
Ran 2 tests with 2 failures and 0 errors
Nachdem die Profile angelegt wurden, sollte jedoch keiner der Tests fehlschlagen:
Ran 2 tests with 0 failures and 0 errors.
-s my.package
, --package my.package
, --dir my.package
durchsucht die angegebenen Verzeichnisse nach Tests.
-m test_setup
, --module test_setup
spezifiziert ein Testmodul als regulären Ausdruck, z.B.:
$ ./bin/test -s my.package -m 'test_setup'
-t '.*installed.*'
, --test test_theme_installed
spezifiziert einen Testfilter als regulären Ausdruck, z.B.:
$ ./bin/test -s vs.policy -m '.*setup.*' -t '.*installed.*'
Hiermit werden im Paket vs.policy
alle, mit installed
endenden,
Methoden in allen Testmodulen, die auf setup
enden, durchlaufen.
-u
, --unit
durchläuft ausschließlich Unit tests und ignoriert andere layer
-Optionen.
-f
, --non-unit
durchläuft alle Tests, die keine Unit Tests sind
-v
, --verbose
führt zu ausführlicherer Ausgabe
--ndiff
falls ein Doctest fehlschlägt, wird ndiff.py
zur Darstellung der
Unterschiede verwendet
--udiff
falls ein Doctest fehlschlägt, wird Unified Diff zur Darstellung der Unterschiede verwendet
--cdiff
falls ein Doctest fehlschlägt, wird Context Diff zur Darstellung der Unterschiede verwendet
-d
, post-mortem
stoppt die Ausführung nach dem ersten nicht-bestandenen Test und ermöglicht post-mortem-Debugging, d.h. die Debug-Session wird nur gestartet, wenn ein Test fehlschlägt.
--path src/my.package
fügt einen Pfad zu Pythons Suchpfad hinzu, wobei die Option mehrfach angegeben werden kann.
Diese erhalten Sie mit:
$ ./bin/test --help
Wenn die relevanten Tests erfolgreich verliefen, sollten schließlich noch alle Tests durchgeführt werden um sicherzustellen, dass nicht an anderer Stelle etwas gebrochen ist. Wenn alle Tests erfolgreich durchlaufen wurden, erscheint eine Meldung:
Ran 10 tests with 0 failures and 0 errors in 4.830 seconds.
Falls nicht alle Tests erfolgreich durchlaufen wurden, ändert sich die Meldung:
Ran 10 tests with 2 failures and 3 errors in 9.688 seconds.
Dabei wurden dann zwei Tests nicht bestanden und drei Tests enthielten Fehler.
roadrunner ist ein Testrunner für Plone 2.5 bis 3.1, der die testgetriebene
Entwicklung deutlich beschleunigen kann, da er vorab das Standard-Zope- und
Plone-Environment für PloneTestCase läd. zur Installation wird einfach folgendes
in die devel.cfg
-Datei eingetragen:
[buildout]
parts =
…
roadrunner
[roadrunner]
recipe = roadrunner:plone
packages-under-test = vs.policy
Anschließend kann es wie der reguläre Zope-Testrunner aufgerufen werden:
$ ./bin/roadrunner -s vs.policy
Ü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.