python - 覆盖Python模块中的 "private"方法

标签 python testing mocking overriding private

我想在 python 中测试一个函数,但它依赖于模块级“私有(private)”函数,我不想调用该函数,但我在覆盖/模拟它时遇到了麻烦。场景:

模块.py

_cmd(command, args):
  # do something nasty

function_to_be_tested():
  # do cool things
  _cmd('rm', '-rf /')
  return 1

测试模块.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 并没有给出正确的引用。使用模拟:

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 是一个模块级函数,我不想将它移动到模块中

最佳答案

[免责声明]

此问题中发布的综合示例有效,并且所描述的问题来自生产代码中的具体实现。也许这个问题应该作为题外话来关闭,因为这个问题是不可重现的。


[注意]对于不耐烦的人解决方案在答案的最后。


无论如何,这个问题给了我一个很好的思考点:当我们无法访问引用所在的变量时,我们如何修补方法引用?

我很多次发现这样的问题。有很多方法可以满足这种情况,常见的是

  • 装饰器:我们想要替换的实例作为装饰器参数传递或在装饰器静态实现中使用
  • 我们想要修补的是方法的默认参数

在这两种情况下,重构代码可能是最好的方法,但是如果我们正在使用一些遗留代码或者装饰器是第三方装饰器怎么办?

好吧,我们已经到了靠墙的地步,但我们使用的是 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

好的,在这种情况下,我们可以通过传递 cmd_fake 来测试 do_work,但在某些情况下不可能做到这一点:例如,如果我们需要调用类似的东西怎么办? :

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():
   ...

现在我应该说除非你已经背水一战,否则永远不要使用这个技巧。测试应该易于理解和调试......尝试调试这样的东西,你会发疯的!

关于python - 覆盖Python模块中的 "private"方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29409040/

相关文章:

postgresql - 从另一个表中选择随机 ID....对 LATERAL JOIN 感到困惑

c# - 无法在单元测试项目中使用 ConfigurationManager

c# - 基于类型对象的模拟接口(interface)

python - 迭代所有像素,如果在范围内,则将像素更改为黑色,否则更改为白色

algorithm - 算法训练阶段的健全性检查

c - 在 C 中运行时打印变量的值

在 iOS chrome 上调试网站

python - Clojure 中的 map 是有序的吗?

python - The Foundry Nuke – 在 QLabel 上显示动画值

python - 在 Pandas 中反转 'one-hot' 编码