使用模拟修补超类方法



这里有许多类似的问题,关于在Python中,你应该如何修补类的超类进行测试。我从他们那里收集了一些想法,但我仍然没有达到我需要的地方。

假设我有两个基类:

class Foo(object):
def something(self, a):
return a + 1
class Bar(object):
def mixin(self):
print("Hello!")

现在我定义要测试的类:

class Quux(Foo, Bar):
def something(self, a):
self.mixin()
return super().something(a) + 2

假设我想测试mixin已被调用,并且我想替换模拟Foo.something的返回值,但重要的是(并且必须(我不想更改Quux.something中的任何控制流或逻辑。假设修补超类"刚刚工作",我尝试unittest.mock.patch

with patch("__main__.Foo", spec=True) as mock_foo:
with patch("__main__.Bar", spec=True) as mock_bar:
mock_foo.something.return_value = 123
q = Quux()
assert q.something(0) == 125
mock_bar.mixin.assert_called_once()

这是行不通的:当Quux实例化时,超类对somethingmixin的定义不会被嘲笑,这并不奇怪,因为类的继承是在补丁之前定义的。

至少,我可以通过显式设置来解决mixin问题:

# This works to mock the mixin method
q = Quux()
setattr(q, "mixin", mock_bar.mixin)

但是,类似的方法不适用于被覆盖的方法,something.

正如我所提到的,这个问题的其他答案建议用模拟覆盖Quux__bases__值。但是,这根本不起作用__bases__因为必须是类的元组,而模拟类似乎只是原始类:

# This doesn't do what I want
Quux.__bases__ = (mock_foo.__class__, mock_bar.__class__)
q = Quux()

其他答案建议覆盖super。这确实有效,但我觉得这有点危险,因为任何你不想修补super的调用都可能会严重破坏事情。

那么有没有比这更好的方法来做我想做的事情:

with patch("builtins.super") as mock_super:
mock_foo = MagicMock(spec=Foo)
mock_foo.something.return_value = 123
mock_super.return_value = mock_foo
mock_bar = MagicMock(spec=Bar)
q = Quux()
setattr(q, "mixin", mock_bar.mixin)
assert q.something(0) == 125
mock_bar.mixin.assert_called_once()

事情其实很简单—— 子类将包含对原始类的引用 在其自己的结构中(公共可见属性__bases____mro__(。当您模拟这些基类时,该引用不会更改 - 模拟只会显式影响使用这些对象的人,而修补是"打开"的。换句话说,仅当您的Quux类本身在with块中定义时,才会使用它们。这也行不通,因为替换类的"模拟"对象不能是真正的超类。

但是,解决方法和正确的方法非常简单 - 您只需要模拟要替换的方法,而不是类。

这个问题现在有点老了,我希望你已经离开了,但正确的做法是:

with patch("__main__.Foo.something", spec=True) as mock_foo:
with patch("__main__.Bar.mixin", spec=True) as mock_bar:
mock_foo.return_value = 123
q = Quux()
assert q.something(0) == 125
mock_bar.assert_called_once()

相关内容

  • 没有找到相关文章

最新更新