devork

E pur si muove

Mocking away the .__init__() method

Saturday, December 06, 2008

I wanted to test a method on a class that didn't really depend on a lot of other stuff off the class. Or rather what it did depend on I had already turned into Mock objects with appropriate .return_values and asserting with .called etc. Problem was that the .__init__() method of the object invoked about half the application framework (option parsing, configuration file loading, setting up logging etc.). Firstly I don't really feel like testing all of that (hey, these are called unittests after all and those fuctionalities have their own!) and secondly then I had to worry about way too much, quite a lot to setup.

That's how the whacky idea of replacing the .__init__() method with a mock occurred to me:

class TestSomething(object):
    @mock.patch('module.Klass.__init__',
                mock.Mock(return_value=None))
    def test_method(self):
        inst = module.Klass()
        inst.other_method = mock.Mock()
        inst._Klass__log = mock.Mock()
        # more of this
        inst.method()
        assert inst.other_method.called

The ugly side effects of deciding to mock away the .__init__() method like this is that I have to create mocks for more internal stuff. The one shown for exampls is normally provided by self.__log = logging.getLogger('foo').

I must admit that I'm still trying to find my way in how to use the mock module effectively and hence I'm not really sure how sane this approach is. One of my objections with this is that not only am I meddling with clearly hidden attributes of the class, but I also have to do this again and again for each test method. So the next revision (I'm using py.test as testing framework here btw):

class TestSomething(object):
    @classmethod
    def setup_class(cls):
        cls._original_init_method = module.Klass.__init__
        module.Klass.__init__ = mock.Mock(return_value=None)

    @classmethod
    def teardown_class(cls):
        module.Klass.__init__ = cls._original_init_method

    def setup_method(self, method):
        self.inst = module.Klass()
        self.inst._Klass__log = mock.Mock()
        # more of this

    def test_method(self):
        self.inst.other_method = mock.Mock()
        self.app.method()
        assert self.inst.other_method.called

This is actually workable and I'm testing what I want to test in a pretty isolated way. I'm still wondering whether I've gone insane or not tough. Is it reasonable to replace .__init__() by mock objects? Have other people done this?

Saturday, December 06, 2008 | Labels: |

5 comments:

Fuzzyman said...

Hello Floris,

As far as I can tell you have two problems.

1) Doing too much work in the constructor making it painful to test / mock. At the least move the stuff you want to avoid into another method (called by __init__) and then mock that instead. (You'll still need to mock it on the class.)

2) You seem to be using '__log' to make it private. The double underscore name mangling is meant to be for preventing issues with subclassing (which may be what you're using it for) and not for privacy. Change it to a single underscore and again it will be less painful to mock.

Also you can avoid having to explicitly create the mock in your use of patch. Try this instead:

@patch('module.Klass.method')
def test_something(self, MockMethod):
....

This replaces both the setup and teardown methods - but if you're doing it in a lot of tests then a class level setup and teardown is a reasonable approach.

If you are just mocking __init__ there is no need to set the return_value to None, but it doesn't do any harm. If you still want to, you can do this inside the body of your test:

MockMethod.return_value = None

Floris Bruynooghe said...

Thanks for the response Micheal,

1) Interesting point, the class could probably do with refactoring in this way. Problem is that it's an old existing class with no tests that I wanted to change, so I created tests for it (covering just the bit I was going to change). Hence it was not really designed with testing in mind.

2) The subclasses need to use their own loggers, that's why '__log' is private. AFAIK that's exactly what '__' is for.

As for the return value, Python does enforce that the return value of __init__() is None, itraises a TypeError if not. And once you have to specify that using a decorator on each test function becomes rather heavy which is why I moved to doing it in setup and teardown manually.

Thanks for making the mock module btw! It's pretty good.

Tartley said...

Miško Hevery has been posting a lot on the Google Testing Blog recently about how to write code that is easy to test:
http://googletesting.blogspot.com/

I've enjoyed it a great deal.

He suggests, as Michael said, not doing any work in your constructor other than simple attribute assignment.

He also suggests that constructing objects inside your __init__ method (or inside anything) makes it hard to test, because you are forcing the test of your constructor to have to deal with loads of other classes. (which sounds similar to what you are suffering from, am I reading that right?)

Instead, the other objects which the constructor needs to do its work should become parameters to your constructor.

Then, this is easy to test, because you can pass in any mocks you like without involving any other real classes.

There is more about this particular solution here, under the heading 'fixing the flaw':

http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

Fuzzyman said...

I didn't know this - thanks!

Python 2.5.2 (r252:60911, Feb 22 2008, 07:57:53)
[GCC 4.0.1 (Apple Computer, Inc. build 5363)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class P(object):
... def __init__(self):
... return 3
...
>>> p = P()
Traceback (most recent call last):
File "stdin", line 1, in module
TypeError: __init__() should return None, not 'int'
>>>

New comments are not allowed.

Subscribe to: Post Comments (Atom)