首页 > 解决方案 > 如何正确修补模块级属性?

问题描述

我有一个用 Python 编写的 Lambda 函数,如下所示:

# lambda_function.py
from data_manager import DataManager

_data_manager = DataManager()

def lambda_handler(event, context):
    return _data_manager.do_something(event['value'])

DataManager 实例存储在模块级属性中,因为创建它有点昂贵。这样,它只在冷启动时创建。它看起来像这样:

# data_manager.py
from os import environ

class DataManager:

    def __init__(self):
        self._api_key = environ['ApiKey']

    def do_something(self, value: int):
        return value * 2

实际上它更复杂,但这说明了我遇到的问题,即我无法弄清楚如何在单元测试中正确修补 DataManager。这是我尝试的第一件事:

# test_lambda_function.py
from unittest.mock import patch

@patch('lambda_function._data_manager', autospec=True)
def test_lambda_handler(mock_data_manager):
    print('test_lambda_handler called')

此测试失败,因为environ['ApiKey']未设置。我的期望是该DataManager.__init__方法不会被调用,因为lambda_function._data_manager已修补,但显然这不起作用。我认为该'test_lambda_handler called'消息甚至从未打印过,因为在解释器处理@patch注释时会发生错误。接下来,我尝试了这个:

# test_lambda_function.py
from unittest.mock import patch

@patch('lambda_function.DataManager', autospec=True)
def test_lambda_handler(mock_data_manager):
    print('test_lambda_handler called')

此测试失败的原因与前一个相同。下一次尝试:

# test_lambda_function.py
from unittest.mock import patch

@patch('data_manager.DataManager', autospec=True)
def test_lambda_handler(mock_data_manager):
    print('test_lambda_handler called')

这个测试通过了,但是当它变得更复杂时我遇到了更多问题,我使用相同的方法添加了另一个测试:

# test_lambda_function.py
from unittest.mock import patch

@patch('data_manager.DataManager', autospec=True)
def test_lambda_handler(mock_data_manager):
    print('test_lambda_handler called')

    event = {'value': 1}
    expected = 2

    mock_data_manager.return_value.do_something.return_value = expected

    from lambda_function import lambda_handler
    actual = lambda_handler(event, {})

    assert expected == actual

@patch('data_manager.DataManager', autospec=True)
def test_lambda_handler_again(mock_data_manager):
    print('test_lambda_handler_again called')

    event = {'value': 2}
    expected = 4

    mock_data_manager.return_value.do_something.return_value = expected

    from lambda_function import lambda_handler
    actual = lambda_handler(event, {})

    assert expected == actual

在这种情况下,第一个测试通过,但第二个测试失败,因为lambda_function._data_manager仍然引用第一个测试中使用的前一个模拟实例。显然,该属性仅在导入模块时分配lambda_function,并且每个测试模块仅导入一次,而不管每个测试lambda_handler单独导入该功能。

我唯一的另一个想法是根本不测试lambda_function模块,但由于各种原因,这并不是一个真正的选择。

标签: pythonunit-testingaws-lambdamockingmonkeypatching

解决方案


推荐阅读