Python monkey patch private function

删除回忆录丶 提交于 2019-12-22 05:43:20

问题


I have a module with a function (call it a()) that calls another function defined in the same module (call it __b()). __b() is a function which speaks to a website via urllib2 and gets some data back. Now I'm trying to test a(), but of course would rather not have my unit tests speak to the public internet. Thus, I'm thinking if I can monkey patch __b() with a function which returns canned data, then I can write the tests for a().

To be more concrete, my module looks kinda like:

def a():
    return __b("someval")

def __b(args):
    return something_complex_with_args

So now I want to test a(), but I need to monkey patch out __b. The problem is that A) the vast majority of information on monkey patching applies to methods of a class, not to functions in a module, and B) the function I want to monkey patch is private. I am willing to change __b to be non-private if it makes the process more feasible, but would rather not.

Suggestions?

Edit: as it stands the test class looks like:

from unittest import TestCase

import mymodule

def newfn(args):
    return {"a" : "b"}

mymodule._b = newfn

class TestMyModule(TestCase):
    def test_basic(self):
        print(mymodule.a('somearg'))

And when I run this, I see the output if the monkey patching had not been done at all, rather than seeing {'a': 'b'} get printed out.


回答1:


I can't seem to reproduce your issue (I've tweaked your example a bit, since it doesn't run at all as-is). Did you just mistype something (like mymodule._b instead of mymodule.__b)?

mymodule.py:

def a(x):
    return __b("someval")

def __b(args):
    return "complex_thingy: {}".format(args)

mytest.py:

from unittest import TestCase

import mymodule

def newfn(args):
    return {"a" : "b"}

mymodule.__b = newfn

class TestMyModule(TestCase):
    def test_basic(self):
        print(mymodule.a('somearg'))

Output:

C:\TEMP>python -m unittest mytest
{'a': 'b'}
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

C:\TEMP>

Seems to work fine.


Or outside of unittest:

mytest2.py:

import mymodule

def newfn(args):
    return {"a" : "b"}

mymodule.__b = newfn

print(mymodule.a('somearg'))

Output:

C:\TEMP>python mytest2.py
{'a': 'b'}

C:\TEMP>



回答2:


If your module was named 'foo', then the following should work.

import foo

def patched_version():
    return 'Hello'

foo.__b = patched_version

print (foo.a())

where foo.py is

def a():
    return __b()

def __b():
    return 'Goodbye'



回答3:


I was facing the same problem, but finally got to the solution. The problem was when using the monkey patch in the unittest.TestCase class. Here's the solution that works just fine:

If you are using Python 2, you'll need to install the "mock" library (http://www.voidspace.org.uk/python/mock/) using easy_install or some other way. This library is already bundled with Python 3.

Here's what the code looks like:

from mock import patch

    class TestMyModule(TestCase):
        def test_basic(self):
            with patch('mymodule._b') as mock:
                mock.return_value={"a" : "b"} # put here what you want the mock function to return. You can make multiple tests varying these values.
                #keep the indentation. Determines the scope for the patch.
                print(mymodule.a('somearg'))

Although this way apparently seems a bit less convenient compared to making a mock function where we could mimic the actual sub-function, _b(), having logic to return different values based on different arguments, but then we unnecessarily add more chances of error. In this approach we just hard-code what we want our mocked function to return and test the actual function we set out to test, that was a().



来源:https://stackoverflow.com/questions/14046623/python-monkey-patch-private-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!