如何在 python 中模拟模块的导入?
How to mock the import of a module in python?
考虑以下项目结构:
/project
- folder/
- some_config_module.py
- func.py
- test/
- __init__.py
- test_config.py
- test.py
假设以下代码
# func.py
import folder.some_config_module as config
def search_for_data():
var = config.predefined_parameter
# rest of code
我现在想为 search_for_data
编写单元测试。为了以明智的方式做到这一点,我需要模拟 some_config_module
的导入,即我的 try
# test.py
import unittest
from unittest.mock import patch
import tests.test_config as test_config
from folder.func import serach_for_data
class TestSearchForData(unittest.TestCase):
@patch('folder.some_config_module')
def test_one(self, test_config):
self.assertEqual(search_for_data(), 0)
if __name__ == "__main__":
unittest.main()
这不会导致预期的行为:我想要的是 test_one
内部的 search_for_data
导入 test.test_config
而不是 folder.some_config_module
。此代码基于 答案...但我似乎误解了一些关于 mock
..
的相当基本的东西
由于您的函数 search_for_data
正在使用在 func
的全局命名空间中导入的名称 config
,因此您需要覆盖它而不是覆盖 folder.some_config_module
。
@patch('folder.func.config')
像import folder.some_config_module as config
这样的导入相当于做:
import folder.some_config_module
config = folder.some_config_module
del folder.some_config_module
因此它将变量 config
添加到该模块 (func.py
) 的命名空间,这就是 search_for_data
正在使用的内容。从 search_for_data
的角度来看,config
是一个引用模块的名称还是引用其他东西并不重要,它只知道它需要从全局命名空间加载该名称(在本例中是模块 func.py
)
在运行时 search_for_data
将在其全局命名空间 (search_for_data.__globals__
) 中查找 config
,模拟修补此全局字典并临时替换此字典中的名称 config
使用模拟对象并在拆卸期间将其恢复。
import os.path as path
from unittest.mock import patch
def func():
print(path)
print(f"Before patch: {func.__globals__['path']}")
with patch("__main__.path"):
print(f"During patch: {func.__globals__['path']}")
func()
print(f"After patch: {func.__globals__['path']}")
输出:
Before patch: <module 'posixpath' from '/usr/lib/python3.8/posixpath.py'>
During patch: <MagicMock name='path' id='140233829017776'>
<MagicMock name='path' id='140233829017776'>
After patch: <module 'posixpath' from '/usr/lib/python3.8/posixpath.py'>
考虑以下项目结构:
/project
- folder/
- some_config_module.py
- func.py
- test/
- __init__.py
- test_config.py
- test.py
假设以下代码
# func.py
import folder.some_config_module as config
def search_for_data():
var = config.predefined_parameter
# rest of code
我现在想为 search_for_data
编写单元测试。为了以明智的方式做到这一点,我需要模拟 some_config_module
的导入,即我的 try
# test.py
import unittest
from unittest.mock import patch
import tests.test_config as test_config
from folder.func import serach_for_data
class TestSearchForData(unittest.TestCase):
@patch('folder.some_config_module')
def test_one(self, test_config):
self.assertEqual(search_for_data(), 0)
if __name__ == "__main__":
unittest.main()
这不会导致预期的行为:我想要的是 test_one
内部的 search_for_data
导入 test.test_config
而不是 folder.some_config_module
。此代码基于 mock
..
由于您的函数 search_for_data
正在使用在 func
的全局命名空间中导入的名称 config
,因此您需要覆盖它而不是覆盖 folder.some_config_module
。
@patch('folder.func.config')
像import folder.some_config_module as config
这样的导入相当于做:
import folder.some_config_module
config = folder.some_config_module
del folder.some_config_module
因此它将变量 config
添加到该模块 (func.py
) 的命名空间,这就是 search_for_data
正在使用的内容。从 search_for_data
的角度来看,config
是一个引用模块的名称还是引用其他东西并不重要,它只知道它需要从全局命名空间加载该名称(在本例中是模块 func.py
)
在运行时 search_for_data
将在其全局命名空间 (search_for_data.__globals__
) 中查找 config
,模拟修补此全局字典并临时替换此字典中的名称 config
使用模拟对象并在拆卸期间将其恢复。
import os.path as path
from unittest.mock import patch
def func():
print(path)
print(f"Before patch: {func.__globals__['path']}")
with patch("__main__.path"):
print(f"During patch: {func.__globals__['path']}")
func()
print(f"After patch: {func.__globals__['path']}")
输出:
Before patch: <module 'posixpath' from '/usr/lib/python3.8/posixpath.py'>
During patch: <MagicMock name='path' id='140233829017776'>
<MagicMock name='path' id='140233829017776'>
After patch: <module 'posixpath' from '/usr/lib/python3.8/posixpath.py'>