如何模拟 python3 中的配置变量?
How to mock config variables in python3?
这是一个 AWS lambda 函数
#service.py
from configs import SENDER, DESTINATIONS
from constants import LOG_FORMAT
import logging
def send_mail(body, SENDER, DESTINATIONS):
...
...
在配置文件中,它正在从 AWS 参数存储中检索数据
# configs.py
from handlers.ssm_handler import load_parameters
from common import constants
import os
environment = os.environ.get(constants.ENVIRONMENT)
JSON_BUCKET = load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MIGRATION_BUCKET)
SENDER = load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MAIL_SENDER)
DESTINATIONS = load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MAIL_DESTINATIONS)
...
所以当我尝试测试它时
# test_service.py
from unittest import TestCase, main, mock
from service import send_mail
class TestMailService(TestCase):
def test_service(self):
with mock.patch('service.SENDER', 'abc@sys.com') as mocked_sender:
with mock.patch('service.DESTINATIONS', 'def@sys.com') as mocked_sender:
with mock.patch('service.logging.Logger.info') as mocked_logging:
send_mail(...)
mocked_logging.assert_called_with('mail sent Successfully')
当我导出 AWS 安全凭证时,此测试用例通过。但它不会,如果我不通过凭据。我猜这是因为在 service.py 文件中它打开了整个 config.py 文件。因此,它需要 sec 凭据才能调用 AWS。
作为解决方案,我尝试模拟 SENDER 和 DESTINATIONS。但它抛出我错误(期待安全令牌)
我希望单元测试独立于安全令牌。提出解决方案
发生这种情况是因为当您导入 configs.py 例如通过 from configs import SENDER, DESTINATION
,它会自动 运行 那些调用 load_parameters
的语句,即使没有活动的 mocks/patches 又会调用 AWS SSM。
解决方案 1
尝试重构 configs.py,使变量的设置仅在显式调用时发生(而不是在导入时发生)。最简单的实现类似于:
configs.py
import os
from common import constants
from handlers.ssm_handler import load_parameters
def get_params():
environment = os.environ.get(constants.ENVIRONMENT)
return {
"SENDER": load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MAIL_SENDER),
"DESTINATIONS": load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MAIL_DESTINATIONS),
}
这需要进行一些重构,因为必须在 AWS Lambda 函数调用的开头插入对 get_params
的调用。这样,使用 AWS SSM 的 load_parameters
的调用将不会自动执行,我们可以在调用之前准备好 mocks/patches。
解决方案 2
当还没有活动的 mock/patch 时,不要导入任何会依次导入 configs.py 的文件。首先修补 load_parameters
,这样它就不会连接到实际的 AWS SSM。您可以手动修补它,也可以使用 moto 中的装饰器 @mock_ssm
。只有这样我们才能安全地导入文件。
from unittest import TestCase, main, mock
from moto import mock_ssm
# from service import send_mail # REMOVE THIS IMPORT!
@mock_ssm # Option 1. Requires <pip install moto>. You have to setup SSM first as usual.
def test_service(mocker): # Requires <pip install pytest-mock>
mocker.patch('handlers.ssm_handler.load_parameters') # Option 2
# with mock.patch('handlers.ssm_handler.load_parameters') as mock_ssm: # Option 3. This is equivalent to Option 2.
mocker.patch('service.SENDER', 'abc@sys.com')
mocker.patch('service.DESTINATIONS', 'def@sys.com')
from service import send_mail # Import it here after the patches have taken effect
send_mail(...)
这是一个 AWS lambda 函数
#service.py
from configs import SENDER, DESTINATIONS
from constants import LOG_FORMAT
import logging
def send_mail(body, SENDER, DESTINATIONS):
...
...
在配置文件中,它正在从 AWS 参数存储中检索数据
# configs.py
from handlers.ssm_handler import load_parameters
from common import constants
import os
environment = os.environ.get(constants.ENVIRONMENT)
JSON_BUCKET = load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MIGRATION_BUCKET)
SENDER = load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MAIL_SENDER)
DESTINATIONS = load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MAIL_DESTINATIONS)
...
所以当我尝试测试它时
# test_service.py
from unittest import TestCase, main, mock
from service import send_mail
class TestMailService(TestCase):
def test_service(self):
with mock.patch('service.SENDER', 'abc@sys.com') as mocked_sender:
with mock.patch('service.DESTINATIONS', 'def@sys.com') as mocked_sender:
with mock.patch('service.logging.Logger.info') as mocked_logging:
send_mail(...)
mocked_logging.assert_called_with('mail sent Successfully')
当我导出 AWS 安全凭证时,此测试用例通过。但它不会,如果我不通过凭据。我猜这是因为在 service.py 文件中它打开了整个 config.py 文件。因此,它需要 sec 凭据才能调用 AWS。 作为解决方案,我尝试模拟 SENDER 和 DESTINATIONS。但它抛出我错误(期待安全令牌)
我希望单元测试独立于安全令牌。提出解决方案
发生这种情况是因为当您导入 configs.py 例如通过 from configs import SENDER, DESTINATION
,它会自动 运行 那些调用 load_parameters
的语句,即使没有活动的 mocks/patches 又会调用 AWS SSM。
解决方案 1
尝试重构 configs.py,使变量的设置仅在显式调用时发生(而不是在导入时发生)。最简单的实现类似于:
configs.py
import os
from common import constants
from handlers.ssm_handler import load_parameters
def get_params():
environment = os.environ.get(constants.ENVIRONMENT)
return {
"SENDER": load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MAIL_SENDER),
"DESTINATIONS": load_parameters(constants.OT_ARCHIVAL_PREFIX+environment+constants.MAIL_DESTINATIONS),
}
这需要进行一些重构,因为必须在 AWS Lambda 函数调用的开头插入对 get_params
的调用。这样,使用 AWS SSM 的 load_parameters
的调用将不会自动执行,我们可以在调用之前准备好 mocks/patches。
解决方案 2
当还没有活动的 mock/patch 时,不要导入任何会依次导入 configs.py 的文件。首先修补 load_parameters
,这样它就不会连接到实际的 AWS SSM。您可以手动修补它,也可以使用 moto 中的装饰器 @mock_ssm
。只有这样我们才能安全地导入文件。
from unittest import TestCase, main, mock
from moto import mock_ssm
# from service import send_mail # REMOVE THIS IMPORT!
@mock_ssm # Option 1. Requires <pip install moto>. You have to setup SSM first as usual.
def test_service(mocker): # Requires <pip install pytest-mock>
mocker.patch('handlers.ssm_handler.load_parameters') # Option 2
# with mock.patch('handlers.ssm_handler.load_parameters') as mock_ssm: # Option 3. This is equivalent to Option 2.
mocker.patch('service.SENDER', 'abc@sys.com')
mocker.patch('service.DESTINATIONS', 'def@sys.com')
from service import send_mail # Import it here after the patches have taken effect
send_mail(...)