[TIP] RFF: Article on python test design pattern - DAI

Aaron Maxwell amax at redsymbol.net
Wed Dec 9 22:56:24 PST 2009


On Tuesday 08 December 2009 02:41:19 pm you wrote:
> In many languages, the parameter "urlopen" in your example is called a
> "callback function".  I don't remember ever seeing callbacks used
> _specifically_ as a test feature, but you can certainly do that.

Really? I use 'em all the time :)  

> By (in your example) exposing urlopen in this way, you have not just
> added a cool test point.  You have also added another way that the user
> can interact with your function/object.  For example, if I need a
> different urlopen than you have provided as your default, it might be
> much easier for me to provide my own urlopen than it would be for me to
> subclass your object and start overriding bits and pieces of it.  You
> already noted this in your description, but as a test feature; I'm
> saying it could also be part of the intended interface.

I think it's important to think about whether you want this to happen at 
design time.  If people start using this test hook for non-test purposes, 
that can only limit your ability to modify or remove the test hook in the 
future.

There are at least three or four possible forms of dependency injection (DI) 
in languages like Python.  The example in the code is a kind I've seen 
called "constructor injection", meaning that an object ctor is passed some 
argument value relevant to the test.  I think this form is more sensitive to 
the question than others (e.g. setting an attribute on the object after 
instantiation).

In real code I tend to mark "test hook only" parameters with an underscore 
prefix - so the signature would be:
  __init__(self, rel_url, _urlopen=urlopen)
And then in the docstring for the method, I make clear it's a testing hook 
that is liable to go away without warning.  (Yes, I'll be sure to write a 
docstring in this case :)  The underscore is just my way of highlighting the 
parameter as special in some way.

Of course, that's if I want it to only be used for testing.  If it is 
something we decide we want to make available outside of that context, and 
commit to keeping around, I won't put up "keep off" signs like that :)

> Others have noted that exception handling inside the function might be a
> problem, but I don't think it is a big deal.  As the author, you will
> know where that exception handling is, so you know what can/can't be
> tested with an assert statement.  If there is a problem, you can use
> some other channel (e.g. globals, message queues) to get the test status
> out.

I tend to agree.  The point is valid, but IMO the advantages way outweigh the 
benefits.

> p.s.  If you design your software carefully, you don't need closures or
> dynamically created functions -- all you need is function pointers, like
> you have in C.  Just write your callback function so that the only
> interface to it is through the parameters and return value.  

I'm not sure I understand.  DAI basically sneaks the execution context of the 
test case inside code that otherwise it couldn't reach.  The big benefit 
being much better reporting of the failure condition.  How would you do the 
same thing without closures?  Am I missing something?

Thanks for your reply, great comments!

-- 
Aaron Maxwell
http://redsymbol.net/



More information about the testing-in-python mailing list