• csdn最新最全pytest系列——pluggy插件源码解读(一)HookspecMarker类和HookimplMarker类分析


    简介

    pluggy是一个非常优秀的插件系统,它是理解pytest的核心,只有理解了pluggy的原理,才能更好的理解和使用pytest,否则见到了pytest的很多应用都会感觉很难理解

    pluggy插件总共的代码量不足一千行,而实现的功能却是如此的强大和好用,这不由得让我们对pytest的源码实现充满了好奇,接下来一段时间就详细的由浅入深的来解读pluggy源码,这个过程中,同样会继续总结一些基础的或者高级的python的知识点。

    当然随着对pluggy源码的深入,也会发现很多在网上书上博客上看不到的pluggy的高级应用,同样本系列都会使用实例演示和分析。

    pluggy的简单应用

    1. import pluggy
    2. # HookspecMarker 和 HookimplMarker 实质上是一个装饰器带参数的装饰器类,作用是给函数增加额外的属性设置
    3. hookspec = pluggy.HookspecMarker("myproject")
    4. hookimpl = pluggy.HookimplMarker("myproject")
    5. # 定义自己的Spec,这里可以理解为定义接口类
    6. class MySpec:
    7. # hookspec 是一个装饰类中的方法的装饰器,为此方法增额外的属性设置,这里myhook可以理解为定义了一个接口
    8. @hookspec
    9. def myhook(self, arg1, arg2):
    10. pass
    11. # 定义了一个插件
    12. class Plugin_1:
    13. # 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的和
    14. @hookimpl
    15. def myhook(self, arg1, arg2):
    16. print("inside Plugin_1.myhook()")
    17. return arg1 + arg2
    18. # 定义第二个插件
    19. class Plugin_2:
    20. # 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的差
    21. @hookimpl
    22. def myhook(self, arg1, arg2):
    23. print("inside Plugin_2.myhook()")
    24. return arg1 - arg2
    25. # 实例化一个插件管理的对象,注意这里的名称要与文件开头定义装饰器的时候的名称一致
    26. pm = pluggy.PluginManager("myproject")
    27. # 将自定义的接口类加到钩子定义中去
    28. pm.add_hookspecs(MySpec)
    29. # 注册定义的两个插件
    30. pm.register(Plugin_1())
    31. pm.register(Plugin_2())
    32. # 通过插件管理对象的钩子调用方法,这时候两个插件中的这个方法都会执行,而且遵循后注册先执行即LIFO的原则,两个插件的结果讲义列表的形式返回
    33. results = pm.hook.myhook(arg1=1, arg2=2)
    34. print(results)

    执行结果如下:

    1. inside Plugin_2.myhook()
    2. inside Plugin_1.myhook()
    3. [-1, 3]

    pluggy的文件组织结构

    pluggy模块总共就有如下6个文件,总共代码行数不到1k行,这6个文件中,caller、hooks.py和manager.py是pluggy的最核心的部分

    1. pluggy
    2. |--------__init__.py
    3. |--------_result.py
    4. |--------_tracing.py
    5. |--------caller.py
    6. |--------hooks.py
    7. |--------manager.py

      自动化测试相关教程推荐:

    2023最新自动化测试自学教程新手小白26天入门最详细教程,目前已有300多人通过学习这套教程入职大厂!!_哔哩哔哩_bilibili

    2023最新合集Python自动化测试开发框架【全栈/实战/教程】合集精华,学完年薪40W+_哔哩哔哩_bilibili

    测试开发相关教程推荐

    2023全网最牛,字节测试开发大佬现场教学,从零开始教你成为年薪百万的测试开发工程师_哔哩哔哩_bilibili

    postman/jmeter/fiddler测试工具类教程推荐

    讲的最详细JMeter接口测试/接口自动化测试项目实战合集教程,学jmeter接口测试一套教程就够了!!_哔哩哔哩_bilibili

    2023自学fiddler抓包,请一定要看完【如何1天学会fiddler抓包】的全网最详细视频教程!!_哔哩哔哩_bilibili

    2023全网封神,B站讲的最详细的Postman接口测试实战教学,小白都能学会_哔哩哔哩_bilibili

    HookspecMarker类和HookimplMarker类分析

    从上面的使用举例看,我们首先是看到了实例化了这两个类的实例,所以,我们就先从这两个类开始分析
    下面看下HookspecMarker类的源码(在hooks.py文件中)

    阅读源码有一个好处就是能发现一些比较高级的语法和比较好的用法,如果觉得读源码有难度,至少说明一点,我们对python的基础语法或者高级语法掌握的还不是很到位。比如下面
    HookspecMarker类的定义,这里面涉及到python的两个相对高级一定的语法,一个是类中call魔法函数的作用,一个就是装饰器类,如果说对这两个知识点不清楚的话,那看到这个类就会有点头大了

    1. class HookspecMarker:
    2. """ Decorator helper class for marking functions as hook specifications.
    3. You can instantiate it with a project_name to get a decorator.
    4. Calling :py:meth:`.PluginManager.add_hookspecs` later will discover all marked functions
    5. if the :py:class:`.PluginManager` uses the same project_name.
    6. """
    7. def __init__(self, project_name):
    8. self.project_name = project_name
    9. def __call__(
    10. self, function=None, firstresult=False, historic=False, warn_on_impl=None
    11. ):
    12. """ if passed a function, directly sets attributes on the function
    13. which will make it discoverable to :py:meth:`.PluginManager.add_hookspecs`.
    14. If passed no function, returns a decorator which can be applied to a function
    15. later using the attributes supplied.
    16. If ``firstresult`` is ``True`` the 1:N hook call (N being the number of registered
    17. hook implementation functions) will stop at I<=N when the I'th function
    18. returns a non-``None`` result.
    19. If ``historic`` is ``True`` calls to a hook will be memorized and replayed
    20. on later registered plugins.
    21. """
    22. def setattr_hookspec_opts(func):
    23. if historic and firstresult:
    24. raise ValueError("cannot have a historic firstresult hook")
    25. setattr(
    26. func,
    27. self.project_name + "_spec",
    28. dict(
    29. firstresult=firstresult,
    30. historic=historic,
    31. warn_on_impl=warn_on_impl,
    32. ),
    33. )
    34. return func
    35. if function is not None:
    36. return setattr_hookspec_opts(function)
    37. else:
    38. return setattr_hookspec_opts

    其实它的本质就是一个装饰器类,如果只把这个类实例化,即不用语法糖加在一个具体的函数上时,即此时返回的是一个setattr_hookimpl_opts,它的参数是另外一个被装饰的函数func,作用就是给被装饰的函数func设置一个属性,属性名就是初始化的时候传入的名称加上”_spec”,属性值时一个字典,字典里面有三个字段,分别是firstresult,historic和warm_on_impl

    如下是HookspecMarker类的两种使用方法

    1. import pluggy
    2. hookspec = pluggy.HookspecMarker("myproject")
    3. @hookspec
    4. def test1():
    5. pass
    6. @pluggy.HookspecMarker("myproject2")
    7. def test2():
    8. pass
    9. print(test1.myproject_spec)
    10. print(test2.myproject2_spec)

    执行结果为:

    1. {'firstresult': False, 'historic': False, 'warn_on_impl': None}
    2. {'firstresult': False, 'historic': False, 'warn_on_impl': None}

    所以HookspecMarker类的本质就是为了给被装饰的函数对象增加这么一个属性

    同理HookimplMarker类的代码如下,也同样是一个装饰器,也是为了给函数增加一个属性,属性名称为HookimplMarker类初始化时给的project_name加上”_impl”,其值主要有5个参数,至于每个参数做什么用的,可以到后面分析manager文件的时候再回头看

    1. class HookimplMarker:
    2. """ Decorator helper class for marking functions as hook implementations.
    3. You can instantiate with a ``project_name`` to get a decorator.
    4. Calling :py:meth:`.PluginManager.register` later will discover all marked functions
    5. if the :py:class:`.PluginManager` uses the same project_name.
    6. """
    7. def __init__(self, project_name):
    8. self.project_name = project_name
    9. def __call__(
    10. self,
    11. function=None,
    12. hookwrapper=False,
    13. optionalhook=False,
    14. tryfirst=False,
    15. trylast=False,
    16. specname=None,
    17. ):
    18. """ if passed a function, directly sets attributes on the function
    19. which will make it discoverable to :py:meth:`.PluginManager.register`.
    20. If passed no function, returns a decorator which can be applied to a
    21. function later using the attributes supplied.
    22. If ``optionalhook`` is ``True`` a missing matching hook specification will not result
    23. in an error (by default it is an error if no matching spec is found).
    24. If ``tryfirst`` is ``True`` this hook implementation will run as early as possible
    25. in the chain of N hook implementations for a specification.
    26. If ``trylast`` is ``True`` this hook implementation will run as late as possible
    27. in the chain of N hook implementations.
    28. If ``hookwrapper`` is ``True`` the hook implementations needs to execute exactly
    29. one ``yield``. The code before the ``yield`` is run early before any non-hookwrapper
    30. function is run. The code after the ``yield`` is run after all non-hookwrapper
    31. function have run. The ``yield`` receives a :py:class:`.callers._Result` object
    32. representing the exception or result outcome of the inner calls (including other
    33. hookwrapper calls).
    34. If ``specname`` is provided, it will be used instead of the function name when
    35. matching this hook implementation to a hook specification during registration.
    36. """
    37. def setattr_hookimpl_opts(func):
    38. setattr(
    39. func,
    40. self.project_name + "_impl",
    41. dict(
    42. hookwrapper=hookwrapper,
    43. optionalhook=optionalhook,
    44. tryfirst=tryfirst,
    45. trylast=trylast,
    46. specname=specname,
    47. ),
    48. )
    49. return func
    50. if function is None:
    51. return setattr_hookimpl_opts
    52. else:
    53. return setattr_hookimpl_opts(function)

    下面用简单的代码演示HookimplMarker装饰器类给函数设置属性的结果

    1. import pluggy
    2. hookspec = pluggy.HookimplMarker("myproject")
    3. @hookspec
    4. def test1():
    5. pass
    6. @pluggy.HookimplMarker("myproject2")
    7. def test2():
    8. pass
    9. print(test1.myproject_impl)
    10. print(test2.myproject2_impl)

    执行结果为:

    1. {'hookwrapper': False, 'optionalhook': False, 'tryfirst': False, 'trylast': False, 'specname': None}
    2. {'hookwrapper': False, 'optionalhook': False, 'tryfirst': False, 'trylast': False, 'specname': None}

    至此 HookspecMarker类和HookimplMarker类的代码就分析完了

     总结:

     光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

    如果对你有帮助的话,点个赞收个藏,给作者一个鼓励。也方便你下次能够快速查找。

    如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步

    在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。

    我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,

    测试开发视频教程、学习笔记领取传送门!!

  • 相关阅读:
    DNS配置
    Pandas数据分析24——pandas时间重采样聚合
    C++复习第二天:类与对象
    pico3pro使用unity播放360全景视频及事件交互
    Java 中的日期时间总结
    PC_输入输出系统/设备_I/O系统(IO接口)基础
    融合正弦余弦和无限折叠迭代混沌映射的蝴蝶优化算法-附代码
    前后端分离
    C语言进阶第三课-----------指针的进阶----------后续版
    java计算机毕业设计文物管理系统源程序+mysql+系统+lw文档+远程调试
  • 原文地址:https://blog.csdn.net/MXB1220/article/details/134561622