Python猴子补丁私有函数
我有一个模块,它有一个函数(称之为Python猴子补丁私有函数,python,monkeypatching,Python,Monkeypatching,我有一个模块,它有一个函数(称之为a()),调用同一模块中定义的另一个函数(称之为\uu b())\uuu b()是一个函数,它通过urllib2与网站对话并获取一些数据。现在我正在尝试测试a(),但当然不希望我的单元测试在公共互联网上进行。因此,我在想,如果我可以使用一个返回固定数据的函数对\uu b()进行monkey-patch,那么我就可以为a()编写测试 更具体地说,我的模块看起来有点像: def a(): return __b("someval") def __b(args
a()
),调用同一模块中定义的另一个函数(称之为\uu b()
)\uuu b()
是一个函数,它通过urllib2
与网站对话并获取一些数据。现在我正在尝试测试a()
,但当然不希望我的单元测试在公共互联网上进行。因此,我在想,如果我可以使用一个返回固定数据的函数对\uu b()
进行monkey-patch,那么我就可以为a()
编写测试
更具体地说,我的模块看起来有点像:
def a():
return __b("someval")
def __b(args):
return something_complex_with_args
所以现在我想测试a()
,但我需要用猴子修补\uu b
。问题是A)关于monkey patch的绝大多数信息适用于类的方法,而不是模块中的函数,B)我想要monkey patch的函数是私有的。我愿意将\u 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'))
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'))
当我运行这个程序时,如果猴子补丁根本没有完成,我会看到输出,而不是看到
{'a':'b'}
被打印出来。如果您的模块名为'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'
我也面临同样的问题,但最终找到了解决办法。问题是在unittest.TestCase类中使用monkey补丁时。以下是一个很好的解决方案: 如果您使用的是Python2,则需要安装“mock”库(http://www.voidspace.org.uk/python/mock/)使用简易安装或其他方式。该库已经与Python 3捆绑在一起 下面是代码的样子:
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'))
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())更不方便,因为它具有基于不同参数返回不同值的逻辑,但是我们不必要地增加了更多出错的机会。在这种方法中,我们只需硬编码我们希望模拟函数返回的内容,并测试我们开始测试的实际函数,即a()。我似乎无法重现您的问题(我对您的示例进行了一些调整,因为它根本没有按原样运行)。你是不是打错了什么东西(比如
mymodule.\u b
而不是mymodule.\u b
)
mymodule.py:
def a(x):
return __b("someval")
def __b(args):
return "complex_thingy: {}".format(args)
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>
import mymodule
def newfn(args):
return {"a" : "b"}
mymodule.__b = newfn
print(mymodule.a('somearg'))
mytest.py:
def a(x):
return __b("someval")
def __b(args):
return "complex_thingy: {}".format(args)
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>
import mymodule
def newfn(args):
return {"a" : "b"}
mymodule.__b = newfn
print(mymodule.a('somearg'))
输出:
def a(x):
return __b("someval")
def __b(args):
return "complex_thingy: {}".format(args)
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>
import mymodule
def newfn(args):
return {"a" : "b"}
mymodule.__b = newfn
print(mymodule.a('somearg'))
似乎工作正常。
或单元测试之外: mytest2.py:
def a(x):
return __b("someval")
def __b(args):
return "complex_thingy: {}".format(args)
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>
import mymodule
def newfn(args):
return {"a" : "b"}
mymodule.__b = newfn
print(mymodule.a('somearg'))
输出:
C:\TEMP>python mytest2.py
{'a': 'b'}
C:\TEMP>
一般来说,如果模块外的任何东西需要访问函数(包括测试),您可能不应该使用
\u name
格式。改用\u name
格式表示它是一个内部函数,同时仍然可以让确实需要访问它的东西访问它。名称损坏(\u x
)与private不同,除非您确切知道它是什么,否则您可能不应该使用它。Python没有数据隐藏。@Lattyware:我知道它和private不一样,但它已经非常接近了。为什么要用双下划线来命名它?@AdamParkin,因为双下划线提供了名称混乱-名称被更改(查看文档了解原因和具体情况)。在Python中,您不需要将内容设置为私有。你的问题清楚地说明了为什么这是个坏主意。Python不是java或C++,写它就好像它会导致坏代码。因此,我仍然不明白为什么它通常是坏的。也许把它移到聊天室,因为它与我的问题不太相关?不,原始版本的uu b()仍然被调用(不管它是\uu b
还是\u b
)。再仔细研究一下,你的答案是有效的,但是当我将打印更改为在测试方法中时(就像我的问题中一样),调用了\uu b
的原始版本,这是因为unittest的执行方式与“普通”Python非常不同。尝试将foo.\uu b=patched\u version
放入测试的setUp
功能中。如果您使用的不是unittest
,那么YMMV。我正在使用unittest
,并将其移动到设置中
并没有解决问题。没有,对我来说仍然不起作用。我得到:AttributeError:没有属性“\b”
将“private”函数添加到init.py使错误消失,但随后修补无效(仍在调用原始版本)。