如何为Python编写的AWS Lambda处理函数编写单元测试——含嵌套内部函数测试方案
嗨,这个问题我之前也碰到过,嵌套在Lambda处理函数里的子函数确实有点棘手,不过有几种靠谱的办法可以搞定单元测试,我给你详细说说:
首先先贴一下你提供的代码,方便对照:
def data_list(event, data_subject): dynamodb = boto3.resource("dynamodb") table = dynamodb.Table("TestTable") print("DATA_LIST") def get_col_name(data_subject): if data_subject == "X": return 'something' elif data_subject == "y": return 'some other things' elif data_subject == "c": return 'really' def another_function(var): pass
方法一:把嵌套函数移到外层(优先推荐)
如果这些子函数的逻辑不依赖外层函数的局部变量(比如上面的dynamodb、table),最简单省心的办法就是把它们抽出来放到data_list外面,这样就能直接单独测试了,完全不用考虑嵌套的问题。
修改后的代码大概是这样:
# 把独立的子函数移到外层 def get_col_name(data_subject): if data_subject == "X": return 'something' elif data_subject == "y": return 'some other things' elif data_subject == "c": return 'really' def another_function(var): pass def data_list(event, data_subject): dynamodb = boto3.resource("dynamodb") table = dynamodb.Table("TestTable") print("DATA_LIST") # 直接调用外层的子函数就行 col_name = get_col_name(data_subject) another_function(...)
测试的时候就太简单了,直接导入子函数写用例:
import unittest from your_module import get_col_name class TestGetColName(unittest.TestCase): def test_col_name_for_x(self): self.assertEqual(get_col_name("X"), "something") def test_col_name_for_y(self): self.assertEqual(get_col_name("y"), "some other things") def test_col_name_for_c(self): self.assertEqual(get_col_name("c"), "really")
方法二:让外层函数暴露嵌套函数(必须保留嵌套时用)
如果子函数必须依赖外层的局部变量(比如要用到table操作数据),那可以修改data_list,让它把内部函数暴露出来——比如作为返回值的一部分,或者挂载到自身属性上。
举个例子,修改data_list返回内部函数:
def data_list(event, data_subject): dynamodb = boto3.resource("dynamodb") table = dynamodb.Table("TestTable") print("DATA_LIST") def get_col_name(data_subject_arg): if data_subject_arg == "X": return 'something' elif data_subject_arg == "y": return 'some other things' elif data_subject_arg == "c": return 'really' def another_function(var): # 比如这里用到了外层的table变量 table.put_item(Item={"var": var}) # 返回内部函数,方便测试 return { "get_col_name": get_col_name, "another_function": another_function }
测试的时候,先调用data_list拿到内部函数的引用,再单独测试:
import unittest from unittest.mock import patch, MagicMock from your_module import data_list class TestNestedFunctions(unittest.TestCase): @patch("boto3.resource") def setUp(self, mock_boto): # mock掉DynamoDB,避免真实调用 mock_table = MagicMock() mock_boto.return_value.Table.return_value = mock_table # 调用外层函数,获取内部函数 self.inner_funcs = data_list({}, "") def test_get_col_name_logic(self): get_col = self.inner_funcs["get_col_name"] self.assertEqual(get_col("X"), "something") self.assertEqual(get_col("y"), "some other things") self.assertEqual(get_col("c"), "really") def test_another_function_behavior(self): another_func = self.inner_funcs["another_function"] another_func("test_value") # 验证是否正确调用了table的put_item方法 self.inner_funcs["another_function"].__closure__[0].cell_contents.put_item.assert_called_once_with( Item={"var": "test_value"} )
这里要注意,闭包变量可以通过__closure__属性访问,不过这个方式有点底层,如果你觉得麻烦,也可以在data_list里把table也一起返回,方便测试时验证。
方法三:通过测试外层函数间接验证嵌套逻辑
如果嵌套函数和外层Lambda的逻辑完全绑定,没法单独抽出来,那可以通过测试data_list的整体行为来间接验证内部函数的正确性。核心思路是mock掉所有外部依赖(比如DynamoDB),然后验证调用data_list时是否触发了预期的行为——而这些行为其实就是嵌套函数的逻辑体现。
比如,假设data_list会用get_col_name的返回值去查询DynamoDB,那测试代码可以这么写:
import unittest from unittest.mock import patch, MagicMock from your_module import data_list class TestDataListIntegration(unittest.TestCase): @patch("boto3.resource") def test_data_list_uses_correct_column_name(self, mock_boto): # 模拟DynamoDB Table对象 mock_table = MagicMock() mock_boto.return_value.Table.return_value = mock_table # 调用Lambda处理函数,传入data_subject="X" data_list({}, "X") # 验证table的query方法是否用了get_col_name返回的"something" # 这里要根据你实际的代码逻辑调整断言参数 mock_table.query.assert_called_once_with( KeyConditionExpression=Key('something').eq(...) # 示例条件 )
这种方式相当于把嵌套函数的测试融入到外层函数的集成测试里,适合逻辑耦合度高的场景。
内容的提问来源于stack exchange,提问作者Amit

