如何修补与被测类在同一文件中的类,即在测试开始之前初始化?
Posted
技术标签:
【中文标题】如何修补与被测类在同一文件中的类,即在测试开始之前初始化?【英文标题】:How do I patch a class in the same file as a class under test, that is initialized before the test begins? 【发布时间】:2017-09-03 11:04:05 【问题描述】:(注意事项:这是对原始问题的大量修改,以包含我错误地省略的细节。)
这是我正在测试的(汇总)文件 (common.py
)。它包含一个装饰器(源自Decorum
library),它调用另一个对象(A
)上的类方法:我想修补A
,因为该代码进行了我没有测试的外部调用。
from decorum import Decorum
class A:
@classmethod
def c(cls):
pass
class ClassyDecorum(Decorum):
"""Hack to allow decorated instance methods of a class object to run with decorators.
Replace this once Decorum 1.0.4+ comes out.
"""
def __get__(self, instance, owner):
from functools import partial
return partial(self.call, instance)
class B(Decorum):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def init(self, *args, **kwargs):
A.c()
return super().init(*args, **kwargs)
我想在我的单元测试中对@patch
类A
进行隔离和检查B.d()
的功能。这是我的单元测试(位于test/test_common.py
):
class BDecoratedClass(MagicMock):
@B
def dummy_func(self):
return "Success"
class TestB(TestCase):
@patch('unittest_experiment.A', autospec=True)
def test_d(self, mock_a):
b = BDecoratedClass()
b.dummy_func()
mock_a.c.assert_called_once_with() # Fails
调试上面的代码,我发现A
从未真正被模拟过:代码进入A
的代码,所以mock_a
从未被调用是有道理的,因此断言失败。但是,我想正确地修补 A
。如果我正在修补 common.py
中存在的导入,则此方法有效,但如果在那里定义了该类,则显然不行?
请注意,我认为这可能是我正在修补的哪里的问题,即@patch('common.A', autospec=True)
更可能类似于@patch('where.python.actually.finds.A.when.B.calls.A', autospec=True)
。但是我非常不清楚如何确定是否是这种情况,如果是这样,正确的路径是什么。例如,@patch('BDecorated.common.A', autospec=True)
不起作用。
【问题讨论】:
@wim 原来的示例代码太简单了。我的错。我已经更新了一个适当的最小、可测试、可验证的示例。 这听起来像是你没有向我们展示的某种进口怪癖。例如,如果A
实际上是在不同的文件中定义并导入了 from
,或者如果您在包方面犯了常见错误之一,最终将同一个文件作为两个模块导入。
什么是b.d
?我没有看到任何这样的方法。
@user2357112 我已经复制了上面的确切示例:类在文件中,如图所示。不过,我同意,被模拟的模块似乎与init()
中调用的模块“不同”。
很遗憾,我没有。
【参考方案1】:
感谢@user2357112,我得到了这个解决方案。警告:我不知道这是标准做法还是“最佳”做法,但它似乎有效。
首先,将BDecoratedClass
移动到test/dummy.py
中它自己的文件中。然后把测试改成这样:
class TestB(TestCase):
@patch('common.A', autospec=True)
def test_d(self, mock_a):
from test.dummy import BDecoratedClass
b = BDecoratedClass()
b.dummy_func()
mock_a.c.assert_called_once_with() # Succeeds
这会强制补丁在导入被修饰的虚拟类之前执行。这有点奇怪,因为导入在函数内部,但对于一个看起来不错的测试。
更大的警告:
这仅适用于从模块导入某些内容的第一个测试,在这种情况下,BDecoratedClass
从中导入。那时,类中的所有其他内容都已加载,无法修补。
【讨论】:
【参考方案2】:看起来补丁替代了导入,这就是为什么到那时为时已晚,除非您按照答案中的解释解决它。我发现对个别方法使用patch.object
也有效。所以像:
class TestB(TestCase):
@patch.object(A, 'c')
def test_d(self, mock_c):
b = BDecoratedClass()
b.dummy_func()
mock_c.assert_called_once_with() # Succeeds
【讨论】:
以上是关于如何修补与被测类在同一文件中的类,即在测试开始之前初始化?的主要内容,如果未能解决你的问题,请参考以下文章