ZopeTestCase and packages
In this wonderful world between Zope 2 and 3, called limbo... I mean Five, there's a fun little trap concerning ZCML, packages as products and ZopeTestCase. I wanted to document it somewhere so here it is.
If you're using packages as products, then to install the package as a product for tests, you'll need the ZCML of the package to be loaded. If the package happens to be included in the configure.zcml of a product,then you'll need Five's loadProducts to have been run in order to install the package as a product. With ZopeTestCase, in order for a product to be available for Five's loadProducts, installProduct must be called before loadProducts is run. Five's loadProducts is usually run in the instance's site.zcml which for Plone tests is usually loaded by the ZCML layer.
The end result is that all installProduct calls must be made *before* loadProducts is run which in turn must be run before any installPackage calls can be made if you do any including of packages as products in product configure.zcml files.
Admittedly, this problem goes away if you don't include the ZCML for packages as products inside a product's ZCML.
If anyone is interested here's an example of using layers to work around this:
from Testing import ZopeTestCase from Products.PloneTestCase import layer ADDONS = [ # Your product dependencies here ] class InstallProducts(object): """Install products before the ZCML layer so that their ZCML will be loaded""" @classmethod def setUp(cls): for dep in ADDONS: if ZopeTestCase.hasProduct(dep): ZopeTestCase.installProduct(dep) @classmethod def tearDown(cls): pass @classmethod def testSetUp(cls): pass @classmethod def testTearDown(cls): pass class InstallPackages(InstallProducts, layer.ZCML): """Install packages after the ZCML layer so that products will have had a chance to include the package ZCML""" @classmethod def setUp(cls): for dep in ADDONS: if ZopeTestCase.hasPackage(dep): ZopeTestCase.installPackage(dep) @classmethod def tearDown(cls): pass @classmethod def testSetUp(cls): pass @classmethod def testTearDown(cls): pass