首页 > 解决方案 > 如何修补“autospec”设置为 True 的对象?

问题描述

我有以下玩具类:

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

类型错误:不能将“autospec”与 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 预期:multiply(3)
E 实际:multiply(<package.module.MyClass object at 0x0000022A4BEEFD00>, 3)

我认为嘲笑autospec=True是一种推荐的做法,但是,很明显,我对它的工作原理有一个错误的理解,尽管我已经阅读了这篇文章这篇文章。

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

标签: pythonmockingpytestmonkeypatchingpytest-mock

解决方案


这实际上是两个不相关的问题。第一个问题源于这样一个事实,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没有绑定到模块,而是一个全局导入(from 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,无论如何。


推荐阅读