Skipping decorators in unit tests

Steven D'Aprano steve+comp.lang.python at pearwood.info
Fri Oct 11 00:36:00 EDT 2013


On Fri, 11 Oct 2013 14:13:19 +1100, Cameron Simpson wrote:

> On 11Oct2013 02:55, Steven D'Aprano
> <steve+comp.lang.python at pearwood.info> wrote:
>> On Fri, 11 Oct 2013 09:12:38 +1100, Cameron Simpson wrote:
>> > Speaking for myself, I would be include to recast this code:
>> > 
>> >   @absolutize
>> >   def addition(a, b):
>> >       return a + b
>> > 
>> > into:
>> > 
>> >   def _addition(a, b):
>> >       return a + b
>> >   addition = absolutize(_addition)
>> > 
>> > Then you can unit test both _addition() and addition().
>> 
>> *shudders*
>> Ew ew ew ew.
> 
> Care to provide some technical discourse here? Aside from losing the
> neat and evocative @decorator syntax, the above is simple and overt.


What part of "Ew ew ew ew" was not technical enough for you? Would it 
help if I add a few extra "ew"s? 

*wink*

But seriously, I don't like doubling the number of names in the namespace 
just for the sake of white-box testing. That means you have to somehow 
document each and every one of the private functions that they aren't for 
using, just for testing.

For the avoidance of doubt, I understand you flagged them as private. But 
even for private use within the module, they're not supposed to be used. 
They are only for testing. So now you have to have a naming convention 
and/or documentation to ensure that they aren't used internally.

If there really is a good use-case for using both the decorated and 
undecorated version of the function (whether internally, or as part of 
the public API), then I'm completely with you. We don't have to use 
decorator syntax if we have good reason to keep the wrapped and unwrapped 
functions separate, just bind them to separate names.

I also like Terry Reedy's suggestion of having the decorator 
automatically add the unwrapped function to the wrapped function as an 
attribute:

def decorate(func):
    @functools.wraps(func)
    def inner(arg):
        blah blah
    inner._unwrapped = func  # make it public if you prefer
    return inner

which makes it all nice and clean and above board. (I seem to recall a 
proposal to have functools.wraps do this automatically...)


>> I would much rather do something like this:
>> 
>> def undecorate(f):
>>     """Return the undecorated inner function from function f.""" return
>>     f.func_closure[0].cell_contents
> 
> Whereas this feels like black magic. Is this portable to any decorated
> function? If so, I'd have hoped it was in the stdlib. If not: black
> magic.

Not every one-line function needs to be in the std lib :-)

To go into the std lib, it would need to be a tad more bullet-proof. For 
instance, it should have better error checking for the case where 
func_closure is None, rather than just raise the cryptic error message:

TypeError: 'NoneType' object is not subscriptable

If would also need to deal with arbitrary closures, where item 0 is not 
necessarily a function, or where there might be multiple functions, or no 
functions at all.

But for purely internal use within a test suite, we can afford to be a 
little more ad hoc and just deal with those cases when and if they occur. 
Since we're white-box testing, we presumably know which functions are 
decorated and which ones aren't (we can read the source code!), and will 
only call undecorate on those which are decorated.

>> And in use:
>> 
>> py> f(100)
>> 201
>> py> undecorate(f)(100)
>> 200
> 
> All lovely, provided you can convince me that undecorate() is robust.
> (And if you can, I'll certainly be filing it away in my funcutils module
> for later use.)

It needs error handling. It assumes that the closure only references a 
single object, namely the function being wrapped. In that sense, it's not 
ready for production as a public utility function. But as a private 
function for use only in testing, under controlled conditions, I think it 
is robust enough, it works in CPython 2.5 through 2.7 and IronPython 2.6. 
In Python 3.x, you have to change func_closure to __closure__, but 
otherwise it works in 3.2 and 3.3. Jython 2.5 seems to choke on the 
decorator syntax:


>>> @decorate
  File "<stdin>", line 1
    @decorate
            ^
SyntaxError: mismatched input '<EOF>' expecting CLASS


which surely is a bug in Jython. If I apply the decorator manually, it 
works.



-- 
Steven



More information about the Python-list mailing list