gpt4 book ai didi

python - 我如何模拟修补 isinstance 测试中使用的类?

转载 作者:行者123 更新时间:2023-11-28 17:08:02 28 4
gpt4 key购买 nike

我想测试功能is_myclass .请帮助我了解如何编写成功的测试。

def is_myclass(obj):
"""This absurd stub is a simplified version of the production code."""
isinstance(obj, MyClass)
MyClass()

文档

unittest.mock 的 Python 文档说明了三种解决 isinstance 问题的方法。问题:

  • 设置spec实际类的参数。
  • 将真实类分配给 __class__属性。
  • 使用spec在真实类的补丁中。

__class__

Normally the __class__ attribute of an object will return its type. For a mock object with a spec, __class__ returns the spec class instead. This allows mock objects to pass isinstance() tests for the object they are replacing / masquerading as:

>>> mock = Mock(spec=3)
>>> isinstance(mock, int)
True

__class__ is assignable to, this allows a mock to pass an isinstance() check without forcing you to use a spec:

>>> mock = Mock()
>>> mock.__class__ = dict
>>> isinstance(mock, dict)
True

[...]

If you use spec or spec_set and patch() is replacing a class, then the return value of the created mock will have the same spec.

>>> Original = Class
>>> patcher = patch('__main__.Class', spec=True)
>>> MockClass = patcher.start()
>>> instance = MockClass()
>>> assert isinstance(instance, Original)
>>> patcher.stop()

测试

我已经编写了五个测试,每个测试首先尝试重现三个解决方案中的每一个,其次对目标代码进行实际测试。典型的模式是 assert isinstance随后调用 is_myclass .

所有测试均失败。

测试 1

这是文档中为使用 spec 提供的示例的完整副本.它使用 spec=<class> 与文档不同而不是 spec=<instance> .它通过本地断言测试,但调用 is_myclass失败,因为 MyClass没有被 mock 。

这等同于 Michele d’Amico 在 isinstance and Mocking 中对类似问题的回答.

测试 2

这是测试 1 的补丁等效项。 spec参数无法设置 __class__模拟的 MyClass 和测试未通过本地 assert isinstance .

测试 3

这是文档中为使用 __class__ 提供的示例的完整副本.它通过本地断言测试,但调用 is_myclass失败,因为 MyClass没有被 mock 。

测试 4

这是测试 3 的补丁等效项。分配给 __class__确实设置了 __class__被 mock 的MyClass但这并没有改变它的类型,所以测试没有通过本地 assert isinstance .

测试 5

这是使用 spec 的紧密副本在打补丁的电话中。它通过了本地断言测试,但只是通过访问 MyClass 的本地副本。由于此局部变量未在 is_myclass 中使用调用失败。

代码

此代码作为独立测试模块编写,旨在在 PyCharm IDE 中运行。您可能需要修改它才能在其他测试环境中运行。

模块 temp2.​​py

import unittest
import unittest.mock as mock


class WrongCodeTested(Exception):
pass


class MyClass:
def __init__(self):
"""This is a simplified version of a production class which must be mocked for unittesting."""
raise WrongCodeTested('Testing code in MyClass.__init__')


def is_myclass(obj):
"""This absurd stub is a simplified version of the production code."""
isinstance(obj, MyClass)
MyClass()


class ExamplesFromDocs(unittest.TestCase):
def test_1_spec(self):
obj = mock.Mock(spec=MyClass)
print(type(MyClass)) # <class 'type'>
assert isinstance(obj, MyClass) # Local assert test passes
is_myclass(obj) # Fail: MyClass instantiated


def test_2_spec_patch(self):
with mock.patch('temp2.MyClass', spec=True) as mock_myclass:
obj = mock_myclass()
print(type(mock_myclass)) # <class 'unittest.mock.MagicMock'>
print(type(MyClass)) # <class 'unittest.mock.MagicMock'>
assert isinstance(obj, MyClass) # Local assert test fails

def test_3__class__(self):
obj = mock.Mock()
obj.__class__ = MyClass
print(type(MyClass)) # <class 'type'>
isinstance(obj, MyClass) # Local assert test passes
is_myclass(obj) # Fail: MyClass instantiated

def test_4__class__patch(self):
Original = MyClass
with mock.patch('temp2.MyClass') as mock_myclass:
mock_myclass.__class__ = Original
obj = mock_myclass()
obj.__class__ = Original
print(MyClass.__class__) # <class 'temp2.MyClass'>
print(type(MyClass)) # <class 'unittest.mock.MagicMock'>
assert isinstance(obj, MyClass) # Local assert test fails

def test_5_patch_with_spec(self):
Original = MyClass
p = mock.patch('temp2.MyClass', spec=True)
MockMyClass = p.start()
obj = MockMyClass()
print(type(Original)) # <class 'type'>
print(type(MyClass)) # <class 'unittest.mock.MagicMock'>
print(type(MockMyClass)) # <class 'unittest.mock.MagicMock'>
assert isinstance(obj, Original) # Local assert test passes
is_myclass(obj) # Fail: Bad type for MyClass

最佳答案

你不能模拟 isinstance() 的第二个参数,不。您找到的文档涉及在第一个 参数通过测试时进行模拟。如果你想产生一些可以接受的东西作为 isinstance() 的第二个参数,你实际上必须有一个 type,而不是一个实例(模拟总是实例) .

您可以使用子类代替 MyClass,这肯定会通过,并且给它一个 __new__ 方法可以让您在尝试调用时更改返回的内容它创建一个实例:

class MockedSubClass(MyClass):
def __new__(cls, *args, **kwargs):
return mock.Mock(spec=cls) # produce a mocked instance when called

并修补它:

mock.patch('temp2.MyClass', new=MockedSubClass)

并使用该类的实例作为模拟:

instance = mock.Mock(spec=MockedSubClass)

或者,这要简单得多,只需使用Mock 作为类,并让obj 成为Mock实例:

with mock.patch('temp2.MyClass', new=mock.Mock) as mocked_class:
is_myclass(mocked_class())

无论哪种方式,您的测试都会通过:

>>> with mock.patch('temp2.MyClass', new=MockedSubClass) as mocked_class:
... instance = mock.Mock(spec=MockedSubClass)
... assert isinstance(instance, mocked_class)
... is_myclass(instance)
...
>>> # no exceptions raised!
...
>>> with mock.patch('temp2.MyClass', new=mock.Mock) as mocked_class:
... is_myclass(mocked_class())
...
>>> # no exceptions raised!
...

对于您的特定测试,以下是它们失败的原因:

  1. 您从未模拟过 MyClass,它仍然引用原始类。第一行is_myclass()成功了,但是第二行用的是原来的MyClass,就被坑了。
  2. MyClass 被替换为 mock.Mock 实例,而不是实际类型,因此 isinstance() 引发了 TypeError: isinstance() arg 2 必须是类型或类型的元组 exception.
  3. 以与 1 完全相同的方式失败,MyClass 完好无损,并且被诱杀。
  4. 失败的方式与 2 相同。 __class__ 是一个只对实例有用的属性。类对象不使用 __class__ 属性,您仍然有一个实例而不是类,并且 isinstance() 会引发类型错误。
  5. 本质上与 4 完全相同,只是您手动启动了修补程序而不是让上下文管理器来处理它,并且您使用了 isinstance(obj, Original) 来检查实例,所以你永远不会在那里遇到类型错误。类型错误改为在 is_myclass() 中触发。

关于python - 我如何模拟修补 isinstance 测试中使用的类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49718428/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com