Python猴子补丁私有功能

时间:2012-12-26 21:43:38

标签: python monkeypatching

我有一个带有函数的模块(称之为a()),它调用同一模块中定义的另一个函数(称之为__b())。 __b()是一个通过urllib2与网站对话并获取一些数据的功能。现在我正在尝试a(),但当然不希望我的单元测试与公共互联网对话。因此,我在想是否可以使用返回固定数据的函数来修补__b(),然后我可以编写a()的测试。

更具体一点,我的模块看起来有点像:

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

def __b(args):
    return something_complex_with_args

所以现在我想测试a(),但我需要修补__b。问题是A)关于猴子补丁的绝大多数信息适用于类的方法,而不适用于模块中的函数,以及B)我想要猴子补丁的函数是私有的。如果它使这个过程更可行,我愿意将__b改为非私人,但不愿意。

建议?

编辑:因为它看起来像测试类:

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'))

当我运行这个时,如果没有完成猴子修补,我会看到输出,而不是看到{'a': 'b'}被打印出来。

3 个答案:

答案 0 :(得分:3)

我似乎无法重现你的问题(我已经稍微调整了你的例子,因为它根本没有运行)。您是否只是输错了某些内容(例如mymodule._b而不是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'))

<强>输出

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

OK

C:\TEMP>

似乎工作正常。


或者在unittest之外:

<强> mytest2.py

import mymodule

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

mymodule.__b = newfn

print(mymodule.a('somearg'))

输出

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

C:\TEMP>

答案 1 :(得分:2)

如果您的模块名为'foo',则以下内容应该有效。

import foo

def patched_version():
    return 'Hello'

foo.__b = patched_version

print (foo.a())

其中foo.py是

def a():
    return __b()

def __b():
    return 'Goodbye'

答案 2 :(得分:0)

我遇到了同样的问题,但终于找到了解决方案。问题是在unittest.TestCase类中使用monkey patch时。这是一个很好的解决方案:

如果您使用的是Python 2,则需要使用easy_install或其他方式安装“mock”库(http://www.voidspace.org.uk/python/mock/)。该库已与Python 3捆绑在一起。

这是代码的样子:

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'))

虽然这种方式显然比制作模拟函数更方便,我们可以模仿实际的子函数_b(),有逻辑基于不同的参数返回不同的值,但是我们不必要地增加了更多的机会错误。在这种方法中,我们只是硬编码我们希望我们的模拟函数返回并测试我们设置为测试的实际函数,即()。