[Grok-dev] Alternatives to Grok for Interface based dependency injection

Chris McDonough chrism at plope.com
Fri May 22 10:00:27 EDT 2009


On 5/22/09 9:14 AM, Alec Munro wrote:
> I think I'm using overloaded terminology here. :)
> When I say "dependency", I'm talking about a run-time dependency of
> the object under test, rather than a compile-time module dependency.
> My recent readings on testability have convinced me that these types
> of dependencies should be in initializer or method signatures, so
> anyone wanting to use an object or method has a clear specification of
> what it depends on. What I found with the adapter pattern is that I
> would often have adapter lookups in the middle of a method block,
> which would mean that method depended on an appropriate adapter being
> registered. I'm sure best practices could be established that would
> avoid this ever becoming problematic, but if possible, I would rather
> use a dependency injection framework that will insist I keep my
> dependencies properly specified.

We use a form of "dependency injection" (if I understand the term right, I don't 
use that particular term for it myself) to help write unit tests.

One sort of epiphany I've personally had lately is that there's just no point 
putting registrations in ZCML (or within grokkers or what-have-you) for 
indirection points that are *only* used for testing.  Here's my experience with 
that.

I'm not a test-driven-development kind of person (although I probably should 
be).  So when I want to code up some functionality on a project, I'll fire up 
Emacs and start writing code.  Let's say I want to add a method to an object 
that obtains and parses an RSS feed.  In the old days, I might have written 
something like this:

     from myproject.interfaces import IFeedParser
     from zope.component import getUtility

     def get_feed_entries(feed_url):
         parser = getUtility(IFeedParser)
         parsed = parser(feed_url)
         return parsed.entries

Then I'd write a test for it:

     import unittest

     class TestGetFeedEntries(unittest.TestCase):
        def test_it(self):
            from zope.component import registerUtility
            from myproject.interfaces import IFeedParser
            from myproject.feeds import get_feed_entries
            class DummyFeed:
                def __init__(self, entries):
                    self.entries = entries
            def dummyfeedparser(url):
                return DummyFeed(['a', 'b'])
            registerUtility(dummyfeedparser, IFeedParser)
            entries = get_feed_entries('http://example.com')
            self.assertEqual(entries, ['a', 'b'])


And (this is the important bit) to ensure that the actual code *would work at 
runtime*, in ZCML, I would have gone and done something like this (I know this 
isn't Grok style, sorry):

    <utility
       component="feedparser.parse"
       provides=".interfaces.IFeedParser"
       />

Over time, this led to a bunch of utility and adapter registrations in ZCML that 
were only there to service test purposes.  Wading through this stuff could be 
painful, especially if there were other registrations in there that really were 
true "policy" plug points.  It's especially bad when new people come on the 
project and can't tell the difference between the registrations that are done 
just to service tests and those that are done because they really are app 
plugpoints.

These days, I write the code using a *default* implementation as a fallback and 
I register *no* ZCML:

     import feedparser
     from myproject.interfaces import IFeedParser
     from zope.component import queryUtility

     def get_feed_entries(feed_url):
         parser = queryUtility(IFeedParser, default=feedparser.parse)
         parsed = parser(feed_url)
         return parsed.entries

The salient difference above is that because I use queryUtility with a default 
that is the "real" implementation instead of using getUtility, I don't need to 
do any ZCML registrations; the tests work the same.

I don't know if this is meaningful for your case in particular, but I think it's 
a pattern worth describing anyway.

- C


More information about the Grok-dev mailing list