【问题标题】:How to patch objects with "autospec" set to True?如何修补“autospec”设置为 True 的对象?
【发布时间】:2021-08-14 15:30:06
【问题描述】:

我有以下玩具类:

class MyClass:

    def __init__(self, x):
        self.x = x

    def get_operator(self):
        answer = input("Multiply? ")
        if answer == "y":
            return "multiply"

    def multiply(self, y):
        if self.get_operator() == "multiply":
            return self.x * y

下面的测试(使用pytest)会报错:

def test_multiply_is_called(mocker):
    multiply = mocker.patch("package.module.MyClass.multiply", return_value=1, autospec=True)
    my_instance = MyClass(2)
    my_instance.multiply(3)
    multiply.assert_called_once()  # no error whatever autospec is equal to (True or False)
    multiply.assert_called_with(3)  # no error only if autospec=False

TypeError: Can't use 'autospec' with create=True

在尝试模拟 Python 内置函数 input 时变得更加不清晰:

def test_input_is_called_once(mocker):
    input = mocker.patch("package.module.input", return_value="y", autospec=True)
    my_instance = MyClass(2)
    my_instance.get_operator()
    input.assert_called_once()  # no error only if autospec=False

E AssertionError:未找到预期的调用。
E 预期:乘法(3)
E 实际:multiply(, 3)

我认为使用 autospec=True 进行嘲弄是一种推荐做法,但很明显,我对它的工作原理有错误的理解,尽管我读过 this postthis one 等。

有人可以澄清一下这个问题吗?

【问题讨论】:

    标签: python mocking pytest monkeypatching pytest-mock


    【解决方案1】:

    这实际上是两个不相关的问题。 第一个问题源于这样一个事实,即使用autospec=False,调用my_instance.multiply(3) 只是归结为使用参数3 对模拟的调用而没有任何检查(例如,一个简单的函数调用)。 但是,如果您使用autospec=True,则方法multiply 绑定到my_instance,就像在实际调用中一样,并作为方法调用,my_instance 作为第一个(self) 参数。因此,要使其正常工作,您需要:

    def test_multiply_is_called(mocker):
        multiply = mocker.patch("package.module.MyClass.multiply", return_value=1, autospec=True)
        my_instance = MyClass(2)
        my_instance.multiply(3)
        multiply.assert_called_with(my_instance, 3)
    

    第二种情况不同,我不得不承认我并不完全清楚它如何与autospec=False 一起正常工作。问题是input 没有绑定到模块,而是一个全局导入(来自builtins),因此它被创建为一个新的模拟对象,而不知道实际的内置函数。在这种情况下,autospec 将无法工作,因为缺少信息。这可以通过模拟内置函数来解决:

    def test_input_is_called_once(mocker):
        input = mocker.patch("builtins.input", return_value="y", autospec=True)
        my_instance = MyClass(2)
        my_instance.get_operator()
        input.assert_called_once()
    

    这适用于autospec=Trueautospec=False。据我了解,使用autospec=False 它可以工作,因为为本地input 函数创建了一个模拟,然后在调用中实际使用它。在我看来,模拟input 的正确方法是模拟builtins.input,无论如何。

    【讨论】:

      猜你喜欢
      • 2012-08-22
      • 2014-10-24
      • 1970-01-01
      • 2017-11-30
      • 2018-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多