简体   繁体   English

如何模拟 python 中模块的导入?

[英]How to mock the import of a module in python?

Consider the following project structure:考虑以下项目结构:

/project
- folder/
    - some_config_module.py
    - func.py
- test/
    - __init__.py
    - test_config.py
    - test.py

Assume the following code假设以下代码

# func.py
import folder.some_config_module as config

def search_for_data():
    var = config.predefined_parameter
    # rest of code

I would now like to write a unit-test for search_for_data .我现在想为search_for_data编写一个单元测试。 To do this in a sensible way, I need to mock the import of the some_config_module , ie my try为了以明智的方式做到这一点,我需要模拟some_config_module的导入,即我的尝试

# 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()

This does not result in the expected behaviour: what I would like is for search_for_data inside of test_one to import test.test_config and not folder.some_config_module .这不会导致预期的行为:我想要的是test_one内的search_for_data导入test.test_config而不是folder.some_config_module This code was based on this answer... But I seem to misunderstand something rather fundamental about mock ..这段代码基于这个答案......但我似乎误解了关于mock的一些相当基本的东西......

Since your function search_for_data is using the name config imported in func 's global namespace, you need to override that instead of the overriding folder.some_config_module .由于您的 function search_for_data使用在func的全局命名空间中导入的名称config ,因此您需要覆盖它而不是覆盖的folder.some_config_module

@patch('folder.func.config')

An import like import folder.some_config_module as config is equivalent to doing:import folder.some_config_module as config这样的导入等效于执行以下操作:

import folder.some_config_module
config = folder.some_config_module
del folder.some_config_module

Hence it adds the variable config to that module( func.py )'s namespace and that's what then search_for_data is using.因此,它将变量config添加到该模块( func.py )的命名空间中,这就是search_for_data正在使用的内容。 From search_for_data 's perspective, it doesn't matter if config is a name that refers to a module or it refers to something else, it just knows that it needs to load that name from the global namespace(in this case it's the module func.py )search_for_data的角度来看, config是一个引用模块的名称还是引用其他东西都没有关系,它只知道它需要从全局命名空间加载该名称(在这种情况下它是模块func.py )


At runtime search_for_data will look for config in its global namespace( search_for_data.__globals__ ), mock patches this globals dictionary and replaces the name config in this dictionary temporarily with the mock object and during teardown it restores it back.在运行时search_for_data将在其全局命名空间( search_for_data.__globals__ )中查找config ,模拟修补此全局字典并将此字典中的名称config临时替换为模拟 object 并在拆卸期间将其恢复。

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']}")

Outputs:输出:

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'>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM