单元测试中Mock helper.py函数及装饰器的正确方法
哈哈,这个坑我当初踩了好多次!你之所以用@patch('project.helpers.function_0', new=lambda: True)没生效,核心原因是你mock错了位置——unittest.mock的patch必须作用在被测试代码实际引用目标对象的命名空间,而不是目标对象原本定义的模块。
下面我结合你给出的myfunction和mydecorator,一步步给你正确的实现方式:
一、正确Mock myfunction的姿势
先明确核心规则:Patch where it's used,不是where it's defined
假设你的项目结构是这样的:
project/ ├── helpers.py # 定义myfunction ├── decorators.py # 定义mydecorator ├── main.py # 业务代码,调用myfunction/使用mydecorator └── tests/ └── test_main.py # 测试用例
如果main.py里是这么调用myfunction的:
# project/main.py from project.helpers import myfunction def process_data(input_val): # 这里调用的是main.py自己导入的myfunction引用 return myfunction(input_val)
那你在test_main.py里,绝对不能直接patchproject.helpers.myfunction,必须patchproject.main.myfunction——因为业务代码里用的是自己命名空间里的那个引用。
测试用例示例
# tests/test_main.py import unittest from unittest.mock import patch from project.main import process_data class TestProcessData(unittest.TestCase): # 用new参数直接替换myfunction为自定义lambda @patch('project.main.myfunction', new=lambda x: f"mocked_result_{x}") def test_mocked_function(self): result = process_data("test_input") self.assertEqual(result, "mocked_result_test_input") # 或者不用new,用return_value自定义返回(适合不需要参数的场景) @patch('project.main.myfunction') def test_function_return_value(self, mock_func): mock_func.return_value = "fixed_mocked_value" result = process_data("any_input") self.assertEqual(result, "fixed_mocked_value") # 还可以验证函数是否被调用 mock_func.assert_called_once_with("any_input")
二、正确Mock mydecorator的姿势
装饰器的特殊之处在于:它是模块导入阶段就执行的,所以必须确保在被测试模块导入前就完成mock,或者用patch的装饰器/上下文管理器来拦截这个过程。
业务代码示例
假设main.py里用了mydecorator:
# project/main.py from project.decorators import mydecorator @mydecorator def decorated_func(input_val): return input_val * 2
测试用例示例
我们需要mockproject.main.mydecorator(同样遵循where it's used的规则),让它返回一个自定义的包装函数(或者直接返回原函数,跳过装饰逻辑):
场景1:完全跳过装饰器逻辑
# tests/test_main.py import unittest from unittest.mock import patch from project.main import decorated_func class TestDecoratedFunc(unittest.TestCase): # 把装饰器替换成“原样返回原函数”的lambda,相当于跳过装饰逻辑 @patch('project.main.mydecorator', new=lambda func: func) def test_skip_decorator(self): # 此时decorated_func的行为就是原函数的行为:input_val*2 result = decorated_func(5) self.assertEqual(result, 10)
场景2:自定义装饰器的行为
如果需要模拟装饰器的特定逻辑,可以给mock的装饰器设置side_effect:
@patch('project.main.mydecorator') def test_custom_decorator_behavior(self, mock_decorator): # 定义自己的mock装饰器逻辑 def mock_wrap(func): def wrapped(input_val, *args, **kwargs): # 比如把输入值乘以10再传给原函数 return func(input_val * 10, *args, **kwargs) return wrapped # 让mock的装饰器使用我们自定义的wrap函数 mock_decorator.side_effect = mock_wrap # 原函数是input_val*2,这里输入5会被装饰器改成50,最终返回100 result = decorated_func(5) self.assertEqual(result, 100)
几个关键注意事项
- 永远牢记:patch的是被测试代码中目标对象的引用路径,不是原定义路径,这是绝大多数mock失效的原因。
- 用
new参数时,确保你的lambda/函数和原函数的参数签名匹配,不然会抛出参数不匹配的错误。 - 对于装饰器,如果你的测试用例是直接导入被装饰后的函数,一定要用
patch装饰测试类/函数,确保mock在模块导入前生效。
内容的提问来源于stack exchange,提问作者bl4ckb1rd
相关产品推荐
相关产品推荐

