History for HowTo
??changed:
-
<style type="text/css"> <!-- li { margin: 1em } --> </style>
(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.":http://diveintopython.org/unit_testing/index.html
Read about what unit tests are and what they are not in Chris !McDonough's
"Unit Testing Zope.":http://zope.org/Members/mcdonc/PDG/UnitTesting
Download and install "!ZopeTestCase.":http://zope.org/Members/shh/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 call 'get_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 calls 'get_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'.
1. In your test module create a test case derived from 'ZopeTestCase'.
2. Override 'afterSetUp' to add your own objects to 'self.folder'.
3. Write one or more tests exercising these objects.
How to setup and run your tests is covered in detail by the
"README":http://zope.org/Members/shh/ZopeTestCaseWiki/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 call 'self.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
[35 more lines...]