如何修补与被测类在同一文件中的类,即在测试开始之前初始化?

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)

我想在我的单元测试中对@patchA 进行隔离和检查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

【讨论】:

以上是关于如何修补与被测类在同一文件中的类,即在测试开始之前初始化?的主要内容,如果未能解决你的问题,请参考以下文章

Grails:我如何模拟被测类的其他方法,这些方法可能在测试期间被内部调用

更新gradle后Android单元测试无法解析被测类

如何在 C++ 中对受保护的方法进行单元测试?

被测类的假方法

如何对继承对象进行单元测试?

Golang 单元测试与性能测试