V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
v2exblog
V2EX  ›  Python

求救! python3.7 如何 mock.patch 一个装饰器装饰过的函数??

  •  
  •   v2exblog · 26 天前 · 659 次点击

    如题:python3.7 如何 mock.patch 一个装饰器装饰过的函数??网上有关于如何 mock 装饰器的例子,但是都跑不通。 比如这个例子就不行 https://www.jianshu.com/p/70a0bc3e3dc4

    # yyy.py
    import functools
    
    
    def timeit(func):
       @functools.wraps(func)
       def wrap(*args, **kwargs):
           print('timeit')
           return func(*args, **kwargs)
    
       return wrap
    
    
    @timeit
    def foo():
       print('foo')
    
    
    # xxx.py
    import functools
    from unittest import TestCase, main
    from unittest import mock
    
    
    def mock_timeit(func):
        @functools.wraps(func)
        def wrap(*args, **kwargs):
            print('mock timeit')
            return func(*args, **kwargs)
    
        return wrap
    
    
    mock.patch('yyy.timeit', mock_timeit).start()
    
    import yyy
    
    
    class YYYTestCase(TestCase):
    
        def test_yyy(self):
            print(yyy.foo())  # 应该输出 mock timeit
    
    
    if __name__ == '__main__':
        main()
    
    
    
    第 1 条附言  ·  26 天前

    解决方案来了。

    首先装饰器必须得用functools.wraps,只有这样被装饰的函数才有__wrapped__(就是原函数) 然后test之前,写这么几句代码。这这这这。。。。。。。:

    # xxx.py
    import functools
    from unittest import TestCase, main
    
    
    def mock_timeit(func):
        @functools.wraps(func)
        def wrap(*args, **kwargs):
            print('mock timeit')
            return func(*args, **kwargs)
    
        return wrap
    
    
    import yyy
    
    
    class YYYTestCase(TestCase):
    
        def test_yyy(self):
            yyy.foo = mock_timeit(yyy.foo.__wrapped__) ## 手动mock
            print(yyy.foo())  # 应该输出 mock timeit
    
    
    if __name__ == '__main__':
        main()
    
    
    
    9 条回复    2021-04-23 19:11:44 +08:00
    no1xsyzy
        1
    no1xsyzy   26 天前   ❤️ 1
    标题陈述有误?
    你这是在 mock 一个装饰器吧

    简单的情况下,你不能。
    yyy:timeit():wrap 已经生成并从 yyy:timeit() 里买定离手了。
    除非你去魔改 yyy:timeit():wrap 的字节码

    当然,如果你高兴的话可以把每一个被 yyy:timeit() 装饰过的函数全部替换为 xxx:mock_timeit() 修饰的版本。
    还有一种,就是修改 yyy:timeit() 的实现方式,把它从一个函数转变为一个 class,其中定义了 yyy:timeit.__around__(self, func)(*args, **kwargs) 。
    v2exblog
        2
    v2exblog   26 天前
    @no1xsyzy 谢谢大佬!一瞬间我就 get 到点了。但是不知道自己应该怎么用代码写出来:(
    aijam
        3
    aijam   26 天前   ❤️ 1
    你需要把 decorator 移到另外一个文件里面,比如 zzz.py 。然后
    mock.patch("zzz.decorator", mock_timeit).start()
    import yyy
    no1xsyzy
        4
    no1xsyzy   26 天前
    @aijam 这也算一个思路,但如果在 zzz 中(或者 import zzz 的过程中间接地) import yyy,也会失效

    如果能改 yyy 的话,最简单的还是把 yyy 重新以 OO 的方式实现,只需要实现 __init__ 和 __call__
    然后 mock timeit.__call__ 就行

    不过,我现在看着看着觉得应该是测试粒度有问题
    SjwNo1
        5
    SjwNo1   26 天前
    @no1xsyzy 所以只能 mock 运行时,不能 mock 函数定义时吗
    abersheeran
        6
    abersheeran   26 天前   ❤️ 1
    @SjwNo1 不能。import 的时候函数定义的执行就已经完成了。
    v2exblog
        7
    v2exblog   26 天前
    @no1xsyzy 确实是,如果想要更好的测试,还是得稍微重构一下代码。
    frostming
        8
    frostming   25 天前
    引用 piglei 的一句话:

    每当你发现很难为代码编写测试时,你就应该意识到代码设计可能存在问题

    https://www.zlovezl.cn/articles/5-tips-on-unit-testing/
    v2exblog
        9
    v2exblog   24 天前
    @frostming 这文章也太棒了,不会是专门为我这个问题写的吧哈哈哈哈哈
    关于   ·   帮助文档   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2837 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 10:25 · PVG 18:25 · LAX 03:25 · JFK 06:25
    ♥ Do have faith in what you're doing.