[Zope-dev] Circular dependency hell.

Martin Aspeli optilude+lists at gmail.com
Tue Apr 20 20:31:07 EDT 2010


Hi Christian,

On 21 April 2010 02:58, Christian Theune <ct at gocept.com> wrote:
> On 04/20/2010 08:44 PM, Jim Fulton wrote:
>>
>> On Tue, Apr 20, 2010 at 12:09 PM, Christian Theune<ct at gocept.com>  wrote:
>>>
>>> Minor note: zope.testing *promotes* layers the wrong way and
>>> zope.app.testing definitely implements them the wrong way.
>>
>> That's prety vague. Could you say specifically in what ways
>> zope.testing promotes layers the wrong way and
>
> zope.testing uses the attribute '__bases__' to store the information what
> the base layers are. __*__ are supposedly Python internal attributes.
> Specifically __bases__ is known to be used to store information which base
> classes a class has.
>
> Looking at this I (and others too) get directed towards: aha, so layers are
> classes and use inheritance to signal what base layers are. Which is exactly
> not what is happening.

In fact, it's a little worse than that. Consider this pair of layers:

class Base:

    @classmethod
    def setUp(cls):
        print "Setting up base"

    @classmethod
    def tearDown(cls):
        print "Tearing down base"

class Child(Base):

    @classmethod
    def setUp(cls):
        print "Setting up child"

Note that there's no tearDown on the child (perhaps it doesn't need
one). What actually happens in this case is that the test runner still
finds a tearDown on Child, it's just that it's inherited from Base. So
in effect, Base's tearDown is called twice.

This also happens with things like testSetUp() and testTearDown(). If
the base defines them and a child doesn't, they're called twice.

The other problem is that it's hard to also use inheritance in the OOP
sense to re-use layer logic.

Also, if the layer manages any state, it has to be set as a class
variable (on cls), which is effectively global. If you want to re-use
a layer but isolate the resources its creates from those created by
existing layers, you have to re-implement the layer.

These insights by Ross Patterson led to collective.testcaselayer,
which was lightly refactored into the layer module of the nascent
plone.testing.

See:

http://svn.plone.org/svn/plone/plone.testing/trunk/src/plone/testing/layer.py
http://svn.plone.org/svn/plone/plone.testing/trunk/src/plone/testing/layer.txt
http://svn.plone.org/svn/plone/plone.testing/trunk/README.txt

This module also contains an implementation of a resource manager that
allows layers to define shared resources in a stack that lets child
layers shadow those resources (i.e. provide a changed fixture). We use
this for things like ZODB connections and Zope 2 app roots. It's
explained best in the README, and tested in layer.txt.

Having used this pattern for a while, I'm pretty sure it's an
improvement on the layers-are-classes thing, which in addition to the
problems above, has caused a fair amount of confusion.

>  > zope.app.testing uses them the wrong way?
>
> Actually it doesn't. I confused this with Zope 2's Testing:
>
> There's Testing/ZopeTestCase/layer.py which defines a class with
> classmethods and in a similar fashion there is Products.PloneTestCase that
> defines classes, derives them and thus kind of piggybacks on the class
> inheritance mechanism to establish __bases__ paired with static methods but
> without actually inheriting methods.

FTR, the ZopeTestCase mess is basically what plone.testing.z2 tries to
fix (insofar as it's possible). The PloneTestCase mess will hopefully
be fixed by a plone.app.testing building on plone.testing.

> We struggled through some hairy details that I fail to remember when we
> worked on gocept.selenium last year which tries to establish a generic layer
> that can be combined with others.

You're not the only one. ;-)

Martin


More information about the Zope-Dev mailing list