如何指定默认为 AttributeError() 且没有显式赋值的 Python 模拟?

How to spec a Python mock which defaults to AttributeError() without explicit assignment?

我有一个与 Python unittest.mock.Mockspec_set 功能相关的问题。

我的目标是创建一个具有以下功能的 Mock:

  1. 它有一个任意的规范 class 我在创建时决定。
  2. 我必须能够根据第 1 点的规范在 mock 上仅分配属性或方法
  3. Mock 必须在以下情况下引发 AttributeError:
    • 我尝试分配一个不在规范中的属性
    • 我调用或检索了一个 属性,它在 spec_set 中缺失,或者在 spec_set 中存在但根据上述点分配。

我想要的一些行为示例:

class MyClass:
   property: int = 5
   def func() -> int:
       pass

# MySpecialMock is the Mock with the functionalities I am dreaming about :D
mock = MyMySpecialMock(spec_set=MyClass)

mock.not_existing # Raise AttributeError
mock.func() # Raise AttributeError
mock.func = lambda: "it works"
mock.func() # Returns "it works"

我尝试了多种解决方案,但都没有成功,或者没有明确地冗长。以下是一些示例:

  1. 使用 Mock(spec_set=...),但它不会在我调用未明确设置的特定属性时引发错误
  2. 使用 Mock(spec_set=...) 并使用具有异常副作用的函数显式覆盖每个属性,但它非常冗长,因为我必须重复所有属性...

我的目标是找到一种使 2 自动化的方法,但我没有干净的方法来做到这一点。你遇到过这样的问题,并解决了吗?

对于好奇的人,目标是能够增强单元测试的分离;我想确保我的模拟仅在我明确设置的方法上被调用,以避免奇怪和意外的副作用。

提前致谢!

spec_set 定义了一个 mock 对象,它与 class 相同,但是不允许对其进行任何更改,因为它定义了特殊的 __getattr____setattr__。这意味着第一个测试(调用不存在的 attr)将按预期失败,但随后尝试设置 attr 也会失败:

from unitest import mock

class X:
    pass

m = mock.Mock(spec_set=X)
m.func()
# __getattr__: AttributeError: Mock object has no attribute 'func'
m.func = lambda: "it works"
# __setattr__: AttributeError: Mock object has no attribute 'func'

相反,您可以使用 create_autospec() 复制现有函数,并向其添加 mock 函数,但不会影响 __setattr__:

n = mock.create_autospec(X)
n.func()
# __getattr__: AttributeError: Mock object has no attribute 'func'
n.func = lambda: "it works"
n.func()
# 'it works'

我想我通过使用 dir 方法找到了我的问题的令人满意的答案。

要根据我上面列出的要求创建 Mock,执行以下操作应该就足够了:

def create_mock(spec: Any) -> Mock:
    mock = Mock(spec_set=spec)

    attributes_to_override = dir(spec)
    for attr in filter(lambda name: not name.startswith("__"), attributes_to_override):
        setattr(mock, attr, Mock(side_effect=AttributeError(f"{attr} not implemented")))

    return mock