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

Mark Sienkiewicz sienkiew at stsci.edu
Thu Dec 10 08:43:40 PST 2009


Aaron Maxwell wrote:
> 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.
>   

At least if you document it, the user has no grounds to complain later 
when the test interface changes.  That doesn't mean they _won't_ 
complain, of course. :)

I think that some test hooks will also make a nice addition to the 
defined interface.  As you noted, the difference is in how you document it.


>> 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?
>   

The test function can literally be a function: parameters in, return 
value out, and no other interaction with anything, except for the 
mechanism used to report that the test passes/fails (you used assert).  
That is a very clean interface, and it doesn't require anything of the 
language beyond function pointers.

In listing 4, it uses the closure feature of python where it says 
"self.assertEqual", but what if that definition said:

    def myurlopen(url) :
       assert('http://example.com/testpath.json' == url)

Python has closures, but I didn't use one here.  I know exactly what and 
where the error is when I get the report of the assertion that detected 
it.  Of course, I could include more information by various mechanisms:

    def myurlopen(url) :
       if 'http://example.com/testpath.json' != url :
          print "Test 1 failed"
          raise AssertionError(repr(url)+" not correct")

In another simplification, the section "DAI Recipe" says to dynamically 
create the function, but a static function would work just as well.  It 
just happens that ALL python functions are dynamically created, and the 
place where you wrote the function definition in your example emphasizes 
that.  If you want to use the technique in a language that doesn't 
dynamically create functions, you would just use static functions:

struct url *myurlopen( char *url )
{
    if (strcmp("http://example.com/testpath.json",url) != 0)
       {
       printf("%s not correct\n",url);
       report_test_fail("myurlopen testing for being called with proper 
URL");
       }
    return NULL;
}

test()
{
    void *x;
    x=RemoteResource("testpath.json", myurlopen);
}

Mark S.





More information about the testing-in-python mailing list