[TIP] ANN: Fudge 1.0 - YAMF (yet another mock framework)

Michael Foord fuzzyman at voidspace.org.uk
Sat Feb 26 10:00:40 PST 2011

On 25/02/2011 22:19, Kumar McMillan wrote:
> Everyone else is doing it, right?!  Mock testing tools abound!
> http://farmdev.com/thoughts/90/fudge-goes-1-0/
> http://farmdev.com/projects/fudge/
> Fudge has come along way since its 0.9.0 release in late 2009.  As the
> flexmock docs point out, one of its major flaws in 0.9 was that you
> had to manage setup/teardown so that expectations got verified.  Since
> 1.0 that is no longer the case.  The docs above explain how it works
> but as a spoiler: I basically stole mock's @patch method.  I always
> liked the way @patch worked but never thought it would add much value
> to Fudge.  Then one day, while working on a mock based suite [1], it
> dawned on me that this would be a perfect way to seamlessly inject
> fudge's verification step.  Unlike flexmock it requires a decorator
> but also unlike flexmock it doesn't couple you to your test runner.
Hey Kumar,

Thanks for the nice things you say about mock. Good to see that mock is 
influencing other testing tools in the Python world.

Forgive me if I'm wrong (which is quite likely), but from a naive 
reading of the implementation of patch in fudge it looks like there are 
quite a few situations it gets 'wrong'. (Monkey patching is actually 
tricky which is exactly why its useful to have tools that do it for you.)

As far as I can tell fudge.patch is doing a getattr / setattr pair to 
get the original attribute and then restore when undoing the patch. It 
also has some special casing for handling cases where setting the new 
attribute (the mock) fails.

If I'm right about how the original attribute is restored [1] then it 
will do the wrong thing (some more serious than others) under the 
following circumstances:

* If you are patching a method on an instance then getattr(obj, 
attr_name) will return a bound method. Actually deleting the object you 
set on the instance is enough to restore the object to its original 
state - setting the bound method as an instance attribute is technically 
incorrect but probably *generally* not a problem.

* If you are patching a class attribute defined on a base class then 
getattr(klass, attr_name) returns the attribute from the base class. 
Resetting that back on the class when the patching is complete is again 
incorrect (merely deleting the monkeypatched attribute is the correct 
behavious). In this case you are permanently modifying the class by 
setting an attribute from a base class onto it. This is probably bad.

* If the attribute is a descriptor like a static method then 
getattr(klass, attr_name) returns an unbound function and not the 
descriptor. When you set that attribute back on the class it becomes a 
method! The correct behaviour is to fetch and restore the original from 
__dict__ rather than using getattr.

You are very welcome to re-use the logic that mock.patch uses to get 
these situations correct. See _path.get_original and _patch.__exit__.

The basic logic for fetching the original is to first check if the name 
exists in obj.__dict__ (handling the case where there is no __dict__). 
If this succeeds then you're doing a local patch and you restore the 
original by putting it back in obj.__dict__.

If it isn't local then you use getattr to fetch the original.

For restoring a non-local patch you can usually restore the original 
state with a delattr, unless there was no __dict__ in which case you 
must use setattr. The exception here is certain kinds of proxy objects 
(like django settings) which proxy attribute access. To avoid this after 
deleting the patched attribute I then check (with hasattr) to ensure the 
attribute is now available. If not then I do a setattr.

Hope this is helpful,

Michael Foord

> [1] Mock is by far the most pervasive mock testing tool.  People love
> it so much that I often lose the vote when trying to introduce Fudge
> into a new project :)  I like mock too but I really don't like
> postmortem inspection and wanted more direct tracebacks.
> Kumar
> _______________________________________________
> testing-in-python mailing list
> testing-in-python at lists.idyll.org
> http://lists.idyll.org/listinfo/testing-in-python


May you do good and not evil
May you find forgiveness for yourself and forgive others
May you share freely, never taking more than you give.
-- the sqlite blessing http://www.sqlite.org/different.html

More information about the testing-in-python mailing list