美文网首页Python Web
Pytest - 如何mock一个装饰器decorator

Pytest - 如何mock一个装饰器decorator

作者: 红薯爱帅 | 来源:发表于2021-05-09 14:27 被阅读0次

    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环境的初始化,例如测试数据库启动和退出、测试数据准备和销毁等。

    相关文章

      网友评论

        本文标题:Pytest - 如何mock一个装饰器decorator

        本文链接:https://www.haomeiwen.com/subject/vdimdltx.html