重写python modu中的“private”方法

2024-04-26 00:38:59 发布

您现在位置:Python中文网/ 问答频道 /正文

我想用python测试一个函数,但是它依赖于一个模块级的“private”函数,我不想调用它,但是在重写/模拟它时遇到了困难。场景:

在模块.py在

_cmd(command, args):
  # do something nasty

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

试验_模块.py在

^{pr2}$

理想情况下,在这个测试中,我不想调用命令。我看了一些其他的线索,我尝试了以下方法,但没有成功:

 test_function():
   def _cmd(command, args):
     # do nothing
     pass
   module._cmd = _cmd

虽然检查module。使用mock:

from mock import patch

def _cmd_mock(command, args):
  # do nothing
  pass

@patch('module._cmd', _cmd_mock) 
test_function():
   ...

在检查模块时提供正确的引用。尽管“function_to_be_tested”仍然使用原始的_cmd(它做了一些令人讨厌的事情就证明了这一点)。在

这很棘手,因为cmd是一个模块级函数,我不想把它移到一个模块中


Tags: 模块to函数pytestcmddefargs
1条回答
网友
1楼 · 发布于 2024-04-26 00:38:59

[免责声明]

这个问题中发布的合成示例起作用,所描述的问题来自于生产代码中的具体实现。也许这个问题应该作为话题外的话题结束,因为这个问题是不可重复的。在


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


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

很多时候我发现了这样的问题。有很多方法可以解决这个问题

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

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

好吧,我们已经有了背景,但是我们使用的是python,在python中没有什么是不可能的。我们需要的只是函数/方法的引用来修补,而不是修补它的引用,我们可以修补__code__:是的,我说的是修补字节码而不是函数。在

举个真实的例子。我使用的默认参数case很简单,但它可以在decorator case中使用。在

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,但是在某些情况下不可能这样做:例如,如果我们需要调用这样的东西,该怎么办:

^{pr2}$

我们可以做的是修补cmd.__code__,而不是{}

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

现在我要说的是,除非你背对着墙,否则不要使用这个技巧。测试应该易于理解和调试。。。试着调试这样的东西,你会发疯的!在

相关问题 更多 >