[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