如何正确模拟实例化类变量的函数?

Posted

技术标签:

【中文标题】如何正确模拟实例化类变量的函数?【英文标题】:How to properly mock a function that instantiates a class variable? 【发布时间】:2021-11-21 20:43:28 【问题描述】:

我的源文件中有这样的东西

# code.py

def some_func():
    # doing some connections and stuff
    return 'someKey': 'someVal'


class ClassToTest:

    var = some_func()

我的测试文件如下所示...我正在尝试模拟 some_func,因为我想避免创建连接。

# test_code.py

from src.code import ClassToTest


def mock_function():
    return "someOtherKey": "someOtherValue"


class Test_Code(unittest.TestCase):

    @mock.patch('src.code.some_func', new=mock_function)
    def test_ClassToTest(self):
        self.assertEqual(ClassToTest.var, "someOtherKey": "someOtherValue")

但这不起作用。另一方面,如果var 是一个即时变量模拟工作正常。我猜这是由于类变量在导入期间被初始化。如何在 var 初始化之前正确模拟 some_func

【问题讨论】:

如果你在修补函数内部进行导入会怎样? 你是说在test_ClassToTest里面?那也不行。 嗯,我猜你还没导入就不能打补丁。我的看法是,您可能想重新考虑在类初始化时(因此在导入时)发生连接逻辑,并改用单例模式。 在导入时不要做“一些连接和东西”。如您所见,它使测试代码变得困难。 您应该同时编写测试和代码,这有助于您以可测试的方式开发代码。编写一些测试来覆盖已经存在且无法更改的代码并没有多大意义——这有点忽略了测试的意义。 【参考方案1】:

当你导入code.py时,还没有激活补丁,所以在初始化ClassToTest.var时,它使用了原来的some_func。只有到那时src.code.some_func 的补丁才会生效,现在显然为时已晚。

解决方案 1

您可以做的是修补some_func,然后重新加载code.py,以便它重新初始化ClassToTest,包括其属性var。因此,由于我们在重新加载 code.py 时已经有一个活动补丁,所以 ClassToTest.var 将被设置为补丁值。

但如果类和修补函数都位于同一个文件中,我们将无法这样做,因此为了使其可测试,请将 some_func 移动到另一个文件,然后将其导入。

src/code.py

from src.other import some_func


class ClassToTest:
    var = some_func()

src/other.py

def some_func():
    # doing some connections and stuff
    return 'realKey': 'realValue'

test_code.py

from importlib import reload
import sys
import unittest
from unittest import mock

from src.code import ClassToTest  # This will always refer to the unpatched version


def mock_function():
    return "someOtherKey": "someOtherValue"


class Test_Code(unittest.TestCase):
    def test_real_first(self):
        self.assertEqual(ClassToTest.var, "realKey": "realValue")

    @mock.patch('src.other.some_func', new=mock_function)
    def test_mock_then_reload(self):
        # Option 1:
        # import src
        # reload(src.code)

        # Option 2
        reload(sys.modules['src.code'])

        from src.code import ClassToTest  # This will be the patched version
        self.assertEqual(ClassToTest.var, "someOtherKey": "someOtherValue")

    def test_real_last(self):
        self.assertEqual(ClassToTest.var, "realKey": "realValue")

输出

$ pytest -q 
...                                                                                           [100%]
3 passed in 0.04s

解决方案 2

如果您不想在测试期间调用真正的some_func,那么仅仅重新加载是不够的。需要做的是永远不要导入包含ClassToTest 的文件,也不要导入任何会间接导入它的文件。仅在some_func 建立有效补丁后才导入它

from importlib import reload
import sys
import unittest
from unittest import mock

# from src.code import ClassToTest  # Remove this import!!!


def mock_function():
    return "someOtherKey": "someOtherValue"


class Test_Code(unittest.TestCase):
    @mock.patch('src.other.some_func', new=mock_function)
    def test_mock_then_reload(self):
        from src.code import ClassToTest  # Move the import here once the patch has taken effect already
        self.assertEqual(ClassToTest.var, "someOtherKey": "someOtherValue")

【讨论】:

解决方案 1 已损坏,因为未打补丁的代码仍在测试期间运行 - “做一些连接和工作” 要么因缺少而失败配置/凭据,或者更糟糕的是,它会在测试期间意外地成功联系到真实的数据库。您必须非常小心地从文件系统、环境变量、默认参数等中删除对配置的任何访问。 解决方案 2 被破坏了,因为它必须修改被测代码,如果这是一个选项,那么有 很多 更好的方法来重构不需要的代码在测试中延迟导入。

以上是关于如何正确模拟实例化类变量的函数?的主要内容,如果未能解决你的问题,请参考以下文章

C函数中如何调用未实例化类的成员函数

如何在 javascript 中调用实例化类的函数?

python如何用字符串实例化类

实例化类的时候代码运行顺序

模拟问题:无法实例化类的代理:Microsoft.AspNetCore.Identity.UserManager`

在实例化类之前在 Python 类中定义的方法的正确术语是啥?