【问题标题】:Python3 mock replace function with another functionPython3 用另一个函数模拟替换函数
【发布时间】:2019-12-23 19:16:50
【问题描述】:

如何在 python 中用另一个函数模拟一个函数,该函数也将提供一个模拟对象?

我有类似这样的代码:

def foo(arg1, arg2):
    r = bar(arg1)
    # does interesting things

我想替换 bar 函数的实现,让它返回合理的值,并确保使用正确的参数调用它。

我已经尝试过了,fake_bar 是我对bar 的简单替换:

from unittest.mock import patch

def test_foo():
   with patch("x.bar", fake_bar) as mock_bar:
      actual = foo(arg1, arg2)
      assert actual == expected
      mock_bar.assert_called_once_with(arg1)

但是,我收到此错误:

AttributeError: 'function' 对象没有属性 'assert_call_once'

我找到了建议使用 create_autospec 的 this question。但是,mock.create_autospec 不保留返回值。

python documentation 建议执行以下操作:

mock_function = create_autospec(function, return_value='fishy')

但是,我伪造的 bar 计算的返回值不是静态的,也不是那么容易放入内联 lambda。

我觉得我错过了一些明显的东西。有没有一种方法可以轻松地模拟一个函数并用比静态返回值更复杂的东西替换它的实现?

【问题讨论】:

    标签: python unit-testing mocking


    【解决方案1】:

    使用wraps 关键字参数:

    def test_foo():
       with patch("x.bar", wraps=fake_bar) as mock_bar:
          actual = foo(arg1, arg2)
          assert actual == expected
          mock_bar.assert_called_once_with(arg1)
    

    这里,x.bar 是您要替换的函数的完全限定名称,例如 'bigbusiness.account.AccountRepository.insert'。

    mock_bar 仍然是Mock(默认情况下),但是Mock 会包装函数fake_bar,所以mock_bar 的返回值就是fake_bar 的返回值,而不是另一个 Mock 实例。

    【讨论】:

    • 很好,我在文档中发现只有一行暗示了这一点,没有示例
    • 也适用于patch.object:patch.object(MyClass, "_MyClass__private_sum", wraps = lambda a, b: a+b)
    • @EzR1d3r 您应该尽可能避免规避名称修改。
    • @chepner 毫无疑问,这是真的。但有时你必须这样做。
    【解决方案2】:

    AttributeError: 'function' 对象没有属性 'assert_call_once

    听起来您正在尝试使用常规功能进行路径。 使用 MagicMock 对象进行修补,该对象的返回值是调用 fake_bar 的结果。

    from unittest.mock import patch, MagicMock
    
    def foo(arg1, arg2):
        r = bar(arg1)
        # does interesting things
    
    def bar(arg):
        return 2
    
    def fake_bar():
        #calculate 2!
        return 2
    
    def test_foo():
        with patch("__main__.bar", MagicMock(return_value=fake_bar())) as mock_bar:
            arg1,arg2 = 'a','b'
            foo(arg1, arg2)
            mock_bar.assert_called_once_with(arg1)
    ##        mock_bar.assert_called_once_with('w')
            print(mock_bar.call_args)
    

    变体:

    def test_foo():
        with patch("__main__.bar", autospec=True) as mock_bar:
            arg1,arg2 = 'a','b'
            mock_bar.return_value = fake_bar()
            foo(arg1, arg2)
            mock_bar.assert_called_once_with(arg1)
            print(mock_bar.call_args)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-17
      • 1970-01-01
      • 2018-01-21
      • 2021-04-19
      • 1970-01-01
      • 1970-01-01
      • 2019-04-19
      • 2018-08-13
      相关资源
      最近更新 更多