python-3.x - 用模拟修补超类方法
问题描述
这里有许多类似的(ish)问题,关于在 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
已被调用并且我想替换 mocked 的返回值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()
这不起作用:超类的定义something
和在实例化mixin
时不会被模拟Quux
,这并不奇怪,因为类的继承是在补丁之前定义的。
mixin
至少,我可以通过明确设置它来解决这个问题:
# This works to mock the mixin method
q = Quux()
setattr(q, "mixin", mock_bar.mixin)
但是,类似的方法不适用于被覆盖的方法,something
.
正如我所提到的,这个问题的其他答案建议用模拟覆盖Quux
's 的__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()
推荐阅读
- java - 错误 无法从 CursorWindow 读取第 0 行 col -1。确保在从光标访问数据之前正确初始化光标
- snowflake-cloud-data-platform - 将 Oracle 查询转换为雪花
- azure - 单一登录到 Azure 门户
- r - 从文本文件的正文中删除 HTML
- javascript - 使用 `expect` 验证 nightwatch 中的 url 返回错误消息
- git - 在 Git 中跟踪多语言项目的最佳方法是什么?
- google-maps - 我可以将“Powered by Google”放在我网站页面的任何位置(页脚)吗?
- sql-server - 使用 MVC 中的多选下拉列表中的选定值更新多列
- intellij-plugin - 在我的 Intellij 插件项目中找不到 PsiClass
- r - 让 dockerized 闪亮的应用程序访问本地卷