使用 unittest.mock.patch 时,为什么默认情况下 autospec 不是 True?
When using unittest.mock.patch, why is autospec not True by default?
当您使用 mock 修补函数时,您可以选择将 autospec 指定为 True:
If you set autospec=True then the mock with be created with a spec
from the object being replaced. All attributes of the mock will also
have the spec of the corresponding attribute of the object being
replaced. Methods and functions being mocked will have their arguments
checked and will raise a TypeError if they are called with the wrong
signature.
(http://www.voidspace.org.uk/python/mock/patch.html)
我想知道为什么这不是默认行为?当然,我们几乎总是希望将不正确的参数传递给我们修补的任何函数?
解释这一点的唯一明确方法是实际引用 documentation 关于使用自动指定的 缺点 以及为什么在使用它时要小心:
This isn’t without caveats and limitations however, which is why it is
not the default behaviour. In order to know what attributes are
available on the spec object, autospec has to introspect (access
attributes) the spec. As you traverse attributes on the mock a
corresponding traversal of the original object is happening under the
hood. If any of your specced objects have properties or descriptors
that can trigger code execution then you may not be able to use
autospec. On the other hand it is much better to design your objects
so that introspection is safe [4].
A more serious problem is that it is common for instance attributes to
be created in the init method and not to exist on the class at
all. autospec can’t know about any dynamically created attributes and
restricts the api to visible attributes.
我认为这里的关键要点是注意这一行:autospec 无法知道任何动态创建的属性并将 api 限制为可见属性
因此,为了帮助更明确地说明自动指定中断的示例,这个取自文档的示例显示了这一点:
>>> class Something:
... def __init__(self):
... self.a = 33
...
>>> with patch('__main__.Something', autospec=True):
... thing = Something()
... thing.a
...
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'a'
如您所见,自动指定并不知道在创建 Something
对象时创建了属性 a
。
为您的实例属性赋值没有错。
观察下面的功能示例:
import unittest
from mock import patch
def some_external_thing():
pass
def something(x):
return x
class MyRealClass:
def __init__(self):
self.a = some_external_thing()
def test_thing(self):
return something(self.a)
class MyTest(unittest.TestCase):
def setUp(self):
self.my_obj = MyRealClass()
@patch('__main__.some_external_thing')
@patch('__main__.something')
def test_my_things(self, mock_something, mock_some_external_thing):
mock_some_external_thing.return_value = "there be dragons"
self.my_obj.a = mock_some_external_thing.return_value
self.my_obj.test_thing()
mock_something.assert_called_once_with("there be dragons")
if __name__ == '__main__':
unittest.main()
所以,我只是说对于我的测试用例,我想确保 some_external_thing()
方法不会影响我的单元测试的行为,所以我只是将我的实例属性分配给每个模拟mock_some_external_thing.return_value = "there be dragons"
.
autospeccing 的动作本身可以执行代码,例如通过调用描述符。
>>> class A:
... @property
... def foo(self):
... print("rm -rf /")
...
>>> a = A()
>>> with mock.patch("__main__.a", autospec=False) as m:
... pass
...
>>> with mock.patch("__main__.a", autospec=True) as m:
... pass
...
rm -rf /
因此,这是一个默认启用的有问题的功能,并且
仅 opt-in。
多年后回答我自己的问题 - 另一个原因是速度。
根据对象的复杂程度,使用 autospec 可能会显着降低测试速度。我在修补 Django 模型时发现了这一点。
当您使用 mock 修补函数时,您可以选择将 autospec 指定为 True:
If you set autospec=True then the mock with be created with a spec from the object being replaced. All attributes of the mock will also have the spec of the corresponding attribute of the object being replaced. Methods and functions being mocked will have their arguments checked and will raise a TypeError if they are called with the wrong signature.
(http://www.voidspace.org.uk/python/mock/patch.html)
我想知道为什么这不是默认行为?当然,我们几乎总是希望将不正确的参数传递给我们修补的任何函数?
解释这一点的唯一明确方法是实际引用 documentation 关于使用自动指定的 缺点 以及为什么在使用它时要小心:
This isn’t without caveats and limitations however, which is why it is not the default behaviour. In order to know what attributes are available on the spec object, autospec has to introspect (access attributes) the spec. As you traverse attributes on the mock a corresponding traversal of the original object is happening under the hood. If any of your specced objects have properties or descriptors that can trigger code execution then you may not be able to use autospec. On the other hand it is much better to design your objects so that introspection is safe [4].
A more serious problem is that it is common for instance attributes to be created in the init method and not to exist on the class at all. autospec can’t know about any dynamically created attributes and restricts the api to visible attributes.
我认为这里的关键要点是注意这一行:autospec 无法知道任何动态创建的属性并将 api 限制为可见属性
因此,为了帮助更明确地说明自动指定中断的示例,这个取自文档的示例显示了这一点:
>>> class Something:
... def __init__(self):
... self.a = 33
...
>>> with patch('__main__.Something', autospec=True):
... thing = Something()
... thing.a
...
Traceback (most recent call last):
...
AttributeError: Mock object has no attribute 'a'
如您所见,自动指定并不知道在创建 Something
对象时创建了属性 a
。
为您的实例属性赋值没有错。
观察下面的功能示例:
import unittest
from mock import patch
def some_external_thing():
pass
def something(x):
return x
class MyRealClass:
def __init__(self):
self.a = some_external_thing()
def test_thing(self):
return something(self.a)
class MyTest(unittest.TestCase):
def setUp(self):
self.my_obj = MyRealClass()
@patch('__main__.some_external_thing')
@patch('__main__.something')
def test_my_things(self, mock_something, mock_some_external_thing):
mock_some_external_thing.return_value = "there be dragons"
self.my_obj.a = mock_some_external_thing.return_value
self.my_obj.test_thing()
mock_something.assert_called_once_with("there be dragons")
if __name__ == '__main__':
unittest.main()
所以,我只是说对于我的测试用例,我想确保 some_external_thing()
方法不会影响我的单元测试的行为,所以我只是将我的实例属性分配给每个模拟mock_some_external_thing.return_value = "there be dragons"
.
autospeccing 的动作本身可以执行代码,例如通过调用描述符。
>>> class A:
... @property
... def foo(self):
... print("rm -rf /")
...
>>> a = A()
>>> with mock.patch("__main__.a", autospec=False) as m:
... pass
...
>>> with mock.patch("__main__.a", autospec=True) as m:
... pass
...
rm -rf /
因此,这是一个默认启用的有问题的功能,并且 仅 opt-in。
多年后回答我自己的问题 - 另一个原因是速度。
根据对象的复杂程度,使用 autospec 可能会显着降低测试速度。我在修补 Django 模型时发现了这一点。