The adapter concept in Zope Component Architecture and the classic `adapter pattern` as described in Design Patterns book are very similar. But the intent of ZCA adapter usage is more wider than the `adapter pattern` itself. The intent of `adapter pattern` is to convert the interface of a class into another interface clients expect. This allows classes work together that couldn't otherwise because of incompatible interfaces. But in the `motivation` section of Design Patterns book, GoF says: "Often the adapter is responsible for functionality the adapted class doesn't provide". ZCA adapter has more focus on adding functionalities than creating a new interface for an adapted object (adaptee). ZCA adapter lets adapter classes extend functionality by adding methods. (It would be interesting to note that `Adapter` was known as `Feature` in earlier stage of ZCA design. ) [#feature]_
The above paragraph has a quote from Gang of Four book, it ends like this: " ...adapted class doesn't provide". But in the next sentence I used "adapted object" instead of "adapted class", because GoF describes about two variants of adapters based on implementations. The first one is called `class adapter` and the other one is called `object adapter`. A class adapter uses multiple inheritance to adapt one interface to another, on the other hand an object adapter relies on object composition. ZCA adapter is following object adapter pattern, which use delegation as a mechanism for composition. GoF's second principle of object-oriented design goes like this: "Favor object composition over class inheritance". For more details about this subject please read Design Patterns book.
The major attraction of ZCA adapter are the explicit interface for components and the component registry. ZCA adapter components are registered in component registry and looked up by client objects using interface and name when required.
.. [#feature] Thread discussing renaming of `Feature` to `Adapter`: http://mail.zope.org/pipermail/zope3-dev/2001-December/000008.html
Testing done very similar to unit tests (that is they're all code based and don't actually go through the browser interface) but require some integration to have been performed. In the case of Plone this often means having a complete plone site installed with all the trimmings. Basically these are unit tests on steriods. 95% of plone add-on developers write their tests these ways (the fastest way to see if a test is an integration test is to see if it uses PloneTestCase or ZopeTestCase ... if it does, it's an integration test). If a test requires a working portal instance, it's an integration test.
Testing from as close to the real environment as possible, in most casee this means using something like selenium or testbrowser (I tend to use testbrowser). These tests never touch actual code api's (other than to run the mock web browser).
. Anyone familiar with the newer Zope 3 style way of coding applications is familar with the module. In this case, as expected, is giving us view classes that ultimately will get accessed via a web browser. Here's how we would write different types of tests for that But before we get into the actual tests, We need to begin by defining
from Products.Five.browser import BrowserView from Products.CMFCore import utils as cmfutils
class SimpleView(BrowserView): """A simple view."""
def nextval(self): portal = cmfutils.getToolByName(self.context, 'portal_url') \ .getPortalObject() current = getattr(portal, '_simpleview_count', 0) portal._simpleview_count = current + 1 return portal._simpleview_count
def __call__(self): return 'Retrieved %i' % self.nextval()
<configure xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser">
<browser:page name="simpleview" for="*" class=".browser.SimpleView" permission="zope.Public" />
</configure>
In general no setup will be performed for this at all. Here the developer would simply import the browser module and instantiate the view classes using python code. And then with the view instance, test each of the methods. In general when any additional functionality is needed, it's done in the form of mock objects. Here's what the test harness looks like (in this example, expected to live as
import unittest from zope.testing import doctest
def test_suite(): return unittest.TestSuite(doctest.DocFileSuite ('unit-example.txt', package='testingexample'))
Here's the test (in doctest-style) as is expected to live in
First some mock objects.
>>> class Mock(object): ... def __init__(self, **kwargs): ... for k, v in kwargs.items(): setattr(self, k, v)
>>> portal = Mock() >>> context = Mock(portal_url=Mock(getPortalObject=lambda: portal))
We don't bother with request since we know the innards of our code and the fact that it doesn't use the request for anything.
>>> from testingexample.browser import SimpleView >>> view = SimpleView(context, None) >>> view.nextval() 1 >>> portal._simpleview_count 1
And adjusting the private var manually will work as expected.
>>> portal._simpleview_count = 50 >>> view.nextval() 51 >>> portal._simpleview_count 51
And then testing the string output of ``__call__``.
>>> view() 'Retrieved 52' >>> portal._simpleview_count 52
The only way to see this break is if someone corrupted the _simpleview_count value (which we should have to account for anyhow).
>>> portal._simpleview_count = 'foobar' >>> view.nextval() Traceback (most recent call last): TypeError: cannot concatenate 'str' and 'int' objects
that sets up a simple plone site and installs any plone add-ons we need. Use code like to look up a view that is being created with and interact with that view component on an api level. Here's what the test harness looks like (in this example, expected to live as
import unittest import testingexample from Testing import ZopeTestCase from Testing.ZopeTestCase.zopedoctest import ZopeDocFileSuite from Products.PloneTestCase import PloneTestCase from Products.PloneTestCase.layer import PloneSite from Products.Five import zcml
PloneTestCase.setupPloneSite()
class MainTestCase(PloneTestCase.PloneTestCase): def afterSetUp(self): zcml.load_config('configure.zcml', testingexample) self.portal._simpleview_count = 0
def test_suite(): suite = ZopeDocFileSuite('integration-example.txt', package='testingexample', test_class=MainTestCase) suite.layer = PloneSite
return unittest.TestSuite((suite,))
In these tests we expect that the portal object has already been setup (ala ``PloneTestCase``) and is available as simply ``portal``.
>>> portal <PloneSite at /plone>
Our first integration test just checks to make sure that we can actually lookup the view by traversing.
>>> view = portal.restrictedTraverse('@@simpleview') >>> view is not None True
Our view instance is already expected to have a working *context* and *request* so we can continue as expected.
>>> view.nextval() 1 >>> portal._simpleview_count 1
And adjusting the private var manually will work as expected.
>>> portal._simpleview_count = 50 >>> view.nextval() 51 >>> portal._simpleview_count 51
And then testing the string output of ``__call__``.
>>> view() 'Retrieved 52' >>> portal._simpleview_count 52
The only way to see this break is if someone corrupted the _simpleview_count value (which we should have to account for anyhow).
>>> portal._simpleview_count = 'foobar' >>> view.nextval() Traceback (most recent call last): TypeError: cannot concatenate 'str' and 'int' objects
Use a setUp that sets up a simple plone site and installs any plone add-ons we need. Instantiate a test browser instance (via ) and mimick browser actions to "log into" the site and access whatever views were produced by browser.py. Here's what the test harness looks like (in this example, expected to live as
import unittest import testingexample from Testing import ZopeTestCase from Testing.ZopeTestCase import FunctionalDocFileSuite from Products.PloneTestCase import PloneTestCase from Products.PloneTestCase.layer import PloneSite from Products.Five import zcml
PloneTestCase.setupPloneSite()
class MainTestCase(PloneTestCase.PloneTestCase): def afterSetUp(self): zcml.load_config('configure.zcml', testingexample) self.portal._simpleview_count = 0
def test_suite(): suite = FunctionalDocFileSuite('functional-example.txt', package='testingexample', test_class=MainTestCase) suite.layer = PloneSite
return unittest.TestSuite((suite,))
These tests are all about seeing and testing what the browser sees. We make no assumptions on the innards of the code -- pretending we have indeed never seen the code itself.
First we need to setup a browser instance.
>>> from Products.Five.testbrowser import Browser >>> browser = Browser()
Now we can start checking things out. Really all we can test here now is that the output to the browser has an integer that increments each time.
>>> browser.open(portal.absolute_url()+'/@@simpleview') >>> browser.contents 'Retrieved 1'
>>> browser.open(portal.absolute_url()+'/@@simpleview') >>> browser.contents 'Retrieved 2'
All of the example code here can be found in the svn collective as an actual python package. Read the included to figure out how to set it up in your own zope instance. The package is available at:
$ ./bin/zopectl test -s testingexample
But don't forget that when developing code you can save yourself a ton of time by maintaining 100% unit test coverage. Then you can run the unit tests as often as you want (at a very rapid speed) and only run the integration and/or functional tests at milestone intervals. To run them separately you would do:
$ ./bin/zopectl test -m testingexample.tests.test_unit $ ./bin/zopectl test -m testingexample.tests.test_integration $ ./bin/zopectl test -m testingexample.tests.test_functional
Testing is great. I'm not particularly advocating test driven development (it works for some people, but other people it does not). But it's important to understand the differences between the different types of tests. The author suggests maintaining 100% unit test coverage and some (client-derived) acceptable amount of functional tests. Integration tests aren't so important when you already have good unit and functional test coverage.
StringField fields as utf-8 encoded str's irrelevant of what the Plone site encoding has been configured as. Safest way to handle this is to always feed StringField field mutators unicode objects instead of str's. And upon retrieval, be prepared to decode the str's using utf-8 (ie context.Title().decode('utf-8') => u'someval'). Remember, StringField accessors will return utf-8 encoded str's and Zope 3 often makes assumptions that the string values it's dealing with are
objects everywhere. If some Zope 2 / Plone API forces you to use 's convert them to