Python单元测试中如何Mock由函数生成的全局变量?
解决Python中Mock全局变量(导入时自动初始化)的问题
问题根源
你当前的测试失效,核心原因是模块导入时机和Mock时机不匹配:
当你执行from request_auth import *时,request_auth.py会被立即加载,其中config = get_config_secret()这行代码会同步执行——此时你的Mock装饰器还没生效,真实的get_config_secret()已经被调用(甚至可能因为缺少环境变量直接报错),后续的Mock无法改变已经初始化完成的config变量。
修复方案
方案1:调整Mock和模块导入的顺序
在测试中,先完成Mock配置,再导入目标模块,确保模块加载时Mock已经生效。修改后的测试代码如下:
import unittest from unittest.mock import patch import os class TestRequestAuth(unittest.TestCase): @patch.dict(os.environ, {"SECRETS_NAME": "test-secret", "AWS_REGION": "us-east-1"}) @patch("secrets.get_config_secret") def test_get_api_root_url(self, mock_get_config_secret): # 先设置Mock返回值 mock_get_config_secret.return_value = {"url": "https://test.test.com/"} # 此时再导入目标函数/模块 from request_auth import get_api_root_url self.assertEqual(get_api_root_url(), "https://test.test.com/")
这里额外加了@patch.dict(os.environ)是为了避免导入secrets.py时,因为找不到SECRETS_NAME和AWS_REGION环境变量抛出异常。
方案2:重构代码,延迟初始化全局变量
如果不想每次测试都调整导入顺序,可以修改request_auth.py的代码逻辑,把config的初始化从模块加载阶段延迟到第一次使用时:
# request_auth.py 修改部分 import requests from secrets import get_config_secret from datetime import datetime, timedelta token = None # 替换原来的直接赋值,改成延迟加载函数 def get_config(): if not hasattr(get_config, "_cached_config"): get_config._cached_config = get_config_secret() return get_config._cached_config def get_api_root_url(): # 调用延迟加载函数获取配置 return get_config()["url"] # 其他用到config的地方也替换成get_config(),比如acquire_token: def acquire_token(): config = get_config() auth_payload = { "grant_type" : "client_credentials", "client_id" : config["id"] } response = requests.post(config["url"], data = auth_payload, verify = verifySSL()) global token token= dict() token["authToken"] = response.json()["access_token"] token["expiry"] = datetime.now() + timedelta(minutes = 10)
修改后,模块加载时不会执行get_config_secret(),只有第一次调用get_config()时才会触发,测试时可以自由Mockget_config_secret(),无需调整导入顺序:
# 测试代码可以简化成 import unittest from unittest.mock import patch import os from request_auth import get_api_root_url class TestRequestAuth(unittest.TestCase): @patch.dict(os.environ, {"SECRETS_NAME": "test-secret", "AWS_REGION": "us-east-1"}) @patch("secrets.get_config_secret") def test_get_api_root_url(self, mock_get_config_secret): mock_get_config_secret.return_value = {"url": "https://test.test.com/"} self.assertEqual(get_api_root_url(), "https://test.test.com/")
额外说明
- 如果你想直接Mock
request_auth里的config变量,必须确保在模块加载后立即patch,但这种方式不如Mock依赖函数灵活,因为其他函数(比如acquire_token)可能会直接读取原始的config值。 - 测试
acquire_token这类依赖网络请求的函数时,还需要额外Mockrequests.post,避免真实发送请求。
内容的提问来源于stack exchange,提问作者ak4550126
相关产品推荐
相关产品推荐

