关于python:在具有动态属性的isinstance检查中使用的模拟类

Mock class used in isinstance checks which has dynamic attributes

有些类在类级别(在__init__或任何其他函数之外)定义其属性(也称为字段)。有些类在它们的__init__函数中定义它们,甚至从其他函数定义它们。有些类使用这两种方法。

1
2
3
4
class MyClass(object):
  foo = 'foo'
  def __init__(self, *args, **kwargs):
    self.bar = 'bar'

问题是,当您使用dir时,如果在类的一个实例中通过,那么它只包括'bar'

1
2
3
4
5
>>> dir(MyClass)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'foo']
>>> myInstance = MyClass()
>>> dir(myInstance)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo']

(滚动到最右边查看差异)

我有一种情况,我需要避免实例化MyClass,但我想使用它作为spec设置(通过isinstance检查),在mock.patch调用单元测试中。

1
2
3
@mock.patch('mypackage.MyClass', spec=MyClass)
def test_thing_that_depends_on_MyClass(self, executeQueryMock):
  # uses 'thing' here, which uses MyClass.bar ...

这样做会导致:

AttributeError: Mock object has no attribute 'bar'

这是有道理的,因为模拟文档说:

spec: This can be either a list of strings or an existing object (a class or instance) that acts as the specification for the mock object. If you pass in an object then a list of strings is formed by calling dir on the object (excluding unsupported magic attributes and methods). Accessing any attribute not in this list will raise an AttributeError.

即使我实例化了MyClass,我也会得到一个不同的错误。

1
2
3
@mock.patch('mypackage.MyClass', spec=MyClass())
def test_thing_that_depends_on_MyClass(self, executeQueryMock):
  # uses 'thing' here, which uses MyClass.bar ...

原因:

TypeError: 'NonCallableMagicMock' object is not callable

我真的不在乎对哪些函数/属性进行严格的访问;我实际上想要正常的magicMock行为,它允许您在不使用AttributeError的情况下调用任何东西。虽然我只是用spec通过isinstance检查,但使用spec似乎使这一点变得严格。

问题:

我如何正确地模拟这个类,这个类在isinstance检查中使用,并且有没有在类级别定义的属性?


考虑到您只想通过isinstance检查,我认为最简单的解决方案是向mock.patch.object编写一个快速包装器,它设置返回的mock的__class__属性。

1
2
3
4
5
def my_patch(obj, attr_name):
    fake_class = mock.MagicMock()
    fake_instance = fake_class.return_value  # calling a class returns an instance.
    fake_instance.__class__ = getattr(obj, attr_name)
    return mock.patch.object(obj, attr_name, fake_class)

它的用法与mock.patch.object相似:

1
2
3
@my_patch(some_module, 'MyClass')
def test_something(self, fake_my_class):
    ...

但是这个伪对象应该通过isinstance检查,就像规范模拟一样。