我想在python中测试一个函数,但它依赖于模块级"私有"功能,我不想要打电话,但我无法覆盖/嘲笑它。情形:
module.py
_cmd(command, args):
# do something nasty
function_to_be_tested():
# do cool things
_cmd('rm', '-rf /')
return 1
test_module.py
import module
test_function():
assert module.function_to_be_tested() == 1
理想情况下,在此测试中我不想调用_cmd。我已经看了一些其他主题,我已经尝试了以下但没有运气:
test_function():
def _cmd(command, args):
# do nothing
pass
module._cmd = _cmd
虽然针对_cmd检查module._cmd并不能给出正确的引用。使用mock:
from mock import patch
def _cmd_mock(command, args):
# do nothing
pass
@patch('module._cmd', _cmd_mock)
test_function():
...
检查module._cmd时,给出了正确的引用,尽管`function_to_be_tested'仍然使用原始的_cmd(由它做出令人讨厌的事情证明)。
这很棘手,因为_cmd是一个模块级函数,我不想把它移到模块中
答案 0 :(得分:2)
<强> [免责声明] 强>
此问题中发布的合成示例有效,所描述的问题来自生产代码中的特定实现。也许这个问题应该作为主题关闭,因为问题不可重复。
[注意] 对于不耐烦的人解决方案就在答案的最后。
无论如何,这个问题给了我一个很好的思考点:当我们无法访问引用所在的变量时,我们如何修补方法引用?
很多次我发现了这样的问题。有很多方法可以满足这种情况,公共场所是
在这两种情况下,重构代码可能是最好的方式,但如果我们正在使用一些遗留代码或者装饰器是第三部分装饰器呢?
好吧,我们背靠墙但是我们正在使用python而在python中没有什么是不可能的。我们需要的只是修补函数/方法的参考,而不是修补它的引用,我们可以修补__code__
:是的我正在谈论修补字节码而不是函数。
得到一个真实的例子。我使用的默认参数大小写很简单,但它可以在装饰器的情况下工作。
def cmd(a):
print("ORIG {}".format(a))
def cmd_fake(a):
print("NEW {}".format(a))
def do_work(a, c=cmd):
c(a)
do_work("a")
cmd=cmd_fake
do_work("b")
输出:
ORIG a ORIG b
Ok在这种情况下,我们可以通过传递do_work
来测试cmd_fake
但是在某些情况下不可能做到这一点:例如,如果我们需要调用类似的东西呢?
def what_the_hell():
list(map(lambda a:do_work(a), ["c","d"]))
我们可以做的是通过
修补cmd.__code__
而不是_cmd
cmd.__code__ = cmd_fake.__code__
所以请按照代码
do_work("a")
what_the_hell()
cmd.__code__ = cmd_fake.__code__
do_work("b")
what_the_hell()
提供以下输出:
ORIG a ORIG c ORIG d NEW b NEW c NEW d
此外,如果我们想要使用模拟,我们可以通过添加以下行来实现:
from unittest.mock import Mock, call
cmd_mock = Mock()
def cmd_mocker(a):
cmd_mock(a)
cmd.__code__=cmd_mocker.__code__
what_the_hell()
cmd_mock.assert_has_calls([call("c"),call("d")])
print("WORKS")
打印出来
WORKS
也许我已经完成了...但OP还在等待他的问题的解决方案
from mock import patch, Mock
cmd_mock = Mock()
#A closure for grabbing the right function code
def cmd_mocker(a):
cmd_mock(a)
@patch.object(module._cmd,'__code__', new=cmd_mocker.__code__)
test_function():
...
现在我应该说从不使用这个技巧,除非你背对墙。测试应该很容易理解和调试...尝试调试这样的东西,你会变得疯狂!