如何正确模拟实例化类变量的函数?
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 被破坏了,因为它必须修改被测代码,如果这是一个选项,那么有 很多 更好的方法来重构不需要的代码在测试中延迟导入。以上是关于如何正确模拟实例化类变量的函数?的主要内容,如果未能解决你的问题,请参考以下文章