HowTo
(Still-terse-but-the-Wiki-has-more Version 0.3)
Introduction
ZopeTestCase is an attempt to make writing unit and regression tests in Zope a more rewarding experience. The package deals with all the plumbing required, and leaves the programmer with writing the tests - and the tests only. It is built on PyUnit? and the Testing package coming with Zope.
ZopeTestCase provides a Zope sandbox to every test. This sandbox comes fully equipped with ZODB connection, root application object, and REQUEST. When writing tests you have complete control over Zope, the way you have from a Python product. The framework is easily customized and can be adapted to virtually every conceivable test situation.
What you can test with ZTC
- Basically everything. Including security, transactions, sessions, web access, ...
What you cannot test with ZTC
- Web forms and user interaction. This should be left to FunctionalTests?.
Prereqs
For an excellent introduction to unit testing read chapter 11 of Mark Pilgrim's Dive Into Python.
Read about what unit tests are and what they are not in Chris McDonough's Unit Testing Zope.
Download and install ZopeTestCase. Be prepared to read the source files, especially the example tests. And the README of course.
What You Get
The ZopeTestCase package consists of the following components:
framework.py
This file must be copied into the individual tests
directories (See the README for more).
It locates the Testing and ZopeTestCase packages, detects INSTANCE_HOME installations,
and determines the custom_zodb.py
file used to set up the test ZODB. framework.py
is
included first thing in every test module.
ZopeLite.py
This module is designed to work like the Zope
module, but to load a lot faster.
The speed-up is achieved by not installing Zope products and help files. If your
tests require a product to be installed (most don't actually), you must install
it yourself using the installProduct
method. ZopeLite
also provides a
hasProduct
method, that allows to test whether a product can be found along the
products path, i.e. whether it is available to the test instance.
ZopeTestCase.py
This module provides the ZopeTestCase
class, which is used as a base class for
all test cases.
A ZopeTestCase sets up a default fixture in the newly started Zope instance. This fixture consist of a folder, a userfolder inside that folder, and a user created in that userfolder. The user's role is configured at folder level and populated with default permissions. Finally, the user is logged in.
This way, once a test runs, a complete and sane Zope "toy" environment is already in place.
The fixture is destroyed during the tearDown phase and can be switched off
entirely by setting _setup_fixture=0
in your test case.
The ZopeTestCase class provides a number of hooks that allow you to adapt the fixture to your needs:
afterSetUp
is called after the default fixture has been set up. Override this method to add the objects you intend to test.This is clearly the most useful hook. You may ignore the remaining hooks until you really need them, if ever.
beforeTearDown
is called at the start of the tearDown phase. The fixture is still in place at this point.afterClear
is called after the fixture has been destroyed. Note that this may occur during setUp and tearDown. This method must not throw an exception even when called repeatedly.
Hooks for controlling transactions:
beforeSetUp
is called before the ZODB connection is opened, at the start of setUp. The default behaviour of this hook is to callget_transaction().begin()
. You will rarely want to override this.beforeClose
is called before the ZODB connection is closed, at the end of tearDown. By default this method callsget_transaction().abort()
to discard any changes made by the test. In some situations you may need to override this hook and commit the transaction instead. Make sure you really know what you are doing though.
utils.py
Provides utility methods to extend the test environment:
setupCoreSessions
creates the Zope sessioning objects in the test ZODB.setupZGlobals
creates the ZGlobals? BTree? required by ZClasses?.setupSiteErrorLog
creates the error_log object required by ZPublisher?.startZServer
starts a ZServer thread on the local host. Use this if the objects you test require URL access to Zope.importObjectFromFile
imports a (.zexp) file into a specified container. Handy for adding "prerolled" components to the sandbox.
These methods must be run at module level. Do not call them from afterSetUp
or from tests!
Writing Tests
Generally, writing tests with ZTC is no different from writing "ordinary" Zope code. A complete Zope environment is made available to every test. The only real difference is that it is not driven by ZServer + ZPublisher? but by the PyUnit? TestRunner?.
Inside a ZopeTestCase the root application object can be accessed as self.app
.
The folder serving as the fixture is available as self.folder
. The REQUEST is
reachable as self.app.REQUEST
.
- In your test module create a test case derived from
ZopeTestCase
. - Override
afterSetUp
to add your own objects toself.folder
. - Write one or more tests exercising these objects.
How to setup and run your tests is covered in detail by the README
The easiest way to start with your own tests is to copy the skeleton test
testSkeleton.py
, and take it from there.
Note that tests are written in unrestricted Python and are not affected by Zope's
security policy. To test the security of individual methods or objects, you must invoke
restrictedTraverse
or validateValue
explicitly!
A simple test may look like:
from Testing import ZopeTestCase from AccessControl import Unauthorized class ExampleTest(ZopeTestCase.ZopeTestCase): def afterSetUp(self): # Add objects to the workarea self.folder.addDTMLMethod('doc', file='foo') def testDocument(self): self.assertEqual(self.folder.doc(), 'foo') def testEditDocument(self): self.folder.doc.manage_edit('bar', '') self.assertEqual(self.folder.doc(), 'bar') def testAccessDocument(self): self.folder.doc.manage_permission('View', ['Manager']) self.assertRaises(Unauthorized, self.folder.restrictedTraverse, 'doc')
Read the Examples
Please study the example tests for more:
testSkeleton.py
represents the simplest possible ZopeTestCase. The module contains all the plumbing required. It is recommended that you copy this file to start your own tests.testPythonScript.py
tests a PythonScript? object in the default fixture. It demonstrates how to manipulate the test user's roles and permissions and how security is validated.testShoppingCart.py
tests the ShoppingCart? example. This test uses Sessions and shows how to test a TTW Zope application.testFunctional.py
demonstrates the new functional testing features. Tests may callself.publish()
to simulate URL calls to the ZPublisher?.testWebserver.py
starts up an HTTP ZServer thread and tests URL access to it. This test is an example for explicit transaction handling and shows how to use ZODB sandboxes to further increase isolation between individual tests.testZopeTestCase.py
tests the ZopeTestCase class itself. May be of interest to the investigative types.testPortalTestCase.py
contains an equivalent test suite for the PortalTestCase base class.testZODBCompat.py
tests various aspects of ZODB behavior in a ZopeTestCase environment. Shows things like cut/paste, import/export, and that v and p variables survive transaction boundaries.
Read the Source
The source files are well documented and an overall worthy read <wink>:
- The ZopeTestCase class is defined in file
ZopeTestCase.py
. - The interfaces implemented by this class are documented in
interfaces.py
. - All names exported by the ZopeTestCase package are listed in
__init__.py
.
Resources
A Wiki with all the documentation: ZopeTestCaseWiki
Slides of my talk at EuroPython 2003: UnitTestingZope
A testrunner with INSTANCE_HOME support: TestRunner
Related
PyUnit (Steve Purcell)
WebUnit (Richard Jones) and WebUnit (Steve Purcell)
ZUnit (Lalo Martins)
FunctionalTests (Tres Seaver)
WebsiteLoadTest (Andreas Jung)
Contact
Feel free to send bug reports, feature requests, etc to stefan at epy.co.at.