Pytest - 如何mock一个装饰器decorator

1. 概述

Python项目的自动化单元测试,可以通过Tox和Pytest完成,提高Python项目的可维护性和健壮性。

关于Tox和Pytest的使用方法,可以参考我之前几篇文章:

本文重点介绍一下通过pytest如何mock一个decorator,应用场景比如有登录权限限制的API函数有无限retry的函数,等。

2. 代码

2.1. 说明

  • Decorator需要是使用functools.wraps生成的,否则,需要参考下面的mock原理,应该也ok
  • 常规的mock方法没有用,例如Mock.return_valueMock.side_effect
  • 正确的mock方法如下,和unitest.mock无关,只是重新创造func
try_mock.func = mock_decorator(try_mock.func.__wrapped__)

2.2. 文件try_mock.py

# filename: try_mock.py
from functools import wraps
import pytest
import mock
import try_mock


def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        print(f'In my_decorator {args}')
        return f(*args, **kwargs)
    return wrapper


def _call(x):
    return '_call {}'.format(x)


@my_decorator
def func(x):
    return 'func {}'.format(_call(x))


const_string = 'mock data 1122'
def mock_decorator(f):
    @wraps(f)
    def decorated_func(*args, **kwargs):
        print(f'In mock_decorator {args}')
        return const_string
        # return f(*args, **kwargs)
    return decorated_func


@mock.patch('try_mock._call', return_value='xx')
def test_func_mock(mock_call):
    for x in [11, 22, 33]:
        ret = func(x)
        assert mock_call.called
        assert mock_call.call_args == ((x,),)
        assert ret == 'func xx'


def test_func_mock1():
    try_mock.func = mock_decorator(try_mock.func.__wrapped__)
    for x in [11, 22, 33]:
        ret = try_mock.func(x)
        assert ret == const_string


if __name__ == '__main__':
    print(try_mock.func(11))

2.3. 正常运行

$ python try_mock.py 
In my_decorator (11,)
func _call 11

2.4. 执行测试

  • 执行单个func的测试
$ pytest -vs try_mock.py::test_func_mock1
====================================================================== test session starts =======================================================================
platform linux -- Python 3.8.5, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /home/shuzhang/soft/miniconda3/bin/python
cachedir: .pytest_cache
rootdir: /home/shuzhang/code/try/python
collected 1 item                                                                                                                                                 

try_mock.py::test_func_mock1 In mock_decorator (11,)
In mock_decorator (22,)
In mock_decorator (33,)
PASSED
  • 执行所有test cases
$ pytest -vs try_mock.py
====================================================================== test session starts =======================================================================
platform linux -- Python 3.8.5, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /home/shuzhang/soft/miniconda3/bin/python
cachedir: .pytest_cache
rootdir: /home/shuzhang/code/try/python
collected 2 items                                                                                                                                                

try_mock.py::test_func_mock In my_decorator (11,)
In my_decorator (22,)
In my_decorator (33,)
PASSED
try_mock.py::test_func_mock1 In mock_decorator (11,)
In mock_decorator (22,)
In mock_decorator (33,)
PASSED

3. 总结

对于Python这种动态语言,项目的自动化单元测试是很有必要的,尤其是在多人协作的团队。
然而,单元测试的开发时间和难度,可能比正常功能的开发要久、要麻烦。尽可能实现即可,宗旨是为正常功能提供一个可靠保障。
遇到的问题,除了本文讲到的Decorator的mock,还有Test环境的初始化,例如测试数据库启动和退出、测试数据准备和销毁等。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容