Python测试框架pytest核心库pluggy详解


Posted in Golang onAugust 05, 2022

代码案例

import pluggy
# HookspecMarker 和 HookimplMarker 实质上是一个装饰器带参数的装饰器类,作用是给函数增加额外的属性设置
hookspec = pluggy.HookspecMarker("myproject")
hookimpl = pluggy.HookimplMarker("myproject")
'''
HookspeckMarker:
    传入firstresult=True时,获取第一个plugin执行结果后就停止继续执行 @hookspec(firstresult=True)
    historic - 表示这个 hook 是需要保存call history 的,当有新的 plugin 注册的时候,需要回放历史
hookimpl:
    当传入tryfirst=True时,表示这个类的hook函数会优先执行,其他的仍然按照后进先出的顺序执行
    当传入trylast=True,表示当前插件的hook函数会尽可能晚的执行,其他的仍然按照后进先出的顺序执行
    当传入hookwrapper=True时,需要在这个plugin中实现一个yield,plugin先执行yield之前的代码,
        然后去执行其他的pluggin,然后再回来执行yield之后的代码,同时通过yield可以获取到其他插件执行的结果
'''
# 定义自己的Spec,这里可以理解为定义接口类
class MySpec:
    # hookspec 是一个装饰类中的方法的装饰器,为此方法增额外的属性设置,这里myhook可以理解为定义了一个接口
    # 会给当前方法添加属性  键为 {self.project_name + "_spec"} 值是装饰器传入的参数
    @hookspec
    def myhook(self, arg1, arg2):
        pass
# 定义了一个插件
class Plugin_1:
    # 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的和
    @hookimpl
    def myhook(self, arg1, arg2):
        print("inside Plugin_1.myhook()")
        return arg1 + arg2
# 定义第二个插件
class Plugin_2:
    # 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的差
    @hookimpl(hookwrapper=True)
    def myhook(self, arg1, arg2):
        out = yield
        print("inside Plugin_2.myhook()")
        return arg1 - arg2
# 实例化一个插件管理的对象,注意这里的名称要与文件开头定义装饰器的时候的名称一致
pm = pluggy.PluginManager("myproject")
# 将自定义的接口类加到钩子定义中去
pm.add_hookspecs(MySpec)
# 注册定义的两个插件
pm.register(Plugin_1())
pm.register(Plugin_2())
# 通过插件管理对象的钩子调用方法,这时候两个插件中的这个方法都会执行,而且遵循后注册先执行即LIFO的原则,两个插件的结果讲义列表的形式返回
results = pm.hook.myhook(arg1=1, arg2=2)
print(results)

实例化:

  • 初始化一些参数,如_name2plugin:存放后续注册 plugin

添加到钩子定义中 (add_hookspecs)

  • 将定义的类已参数的方式传递进去 (module_or_class)
  • 遍历类中全部的方法
  • 判断: getattr(method, self.project_name + "_spec", None),判断类方法中是否有当前定义 myproject+spec 的属性,
    • 如果有则返回装饰器所得到的参数,没有则返回 None,其实就是判断有没有被@hookspec装饰,因为装饰了会设置上 myproject+spec 这个属性以及相对应的值
  • 如果有被装饰: 判断一下 self.hook 中是否以及存在了这个 spec
  • 如果不存在: 创建一个_HookCaller(spec 名字,_hookexec(本质就是一个执行 hook 的方法),传递进来的 spec 对象,第三步获得的参数,也就是通过装饰器 set 到方法中的一些参数) 对象
    • init: 判断 spec 对象是否为空,如果不为空:
      • ​ 先判断参数是否存在,如果存在,创建一个 HookSpec 给 self.spec,传递参数为 当前 spec 对象,当前 spec 名字,参数,self.function 就是对应的那个被装饰的方法,最后判断一下这个 spec 需不需要保存历史,如果需要,初始化一个列表
  • 将上面初始化的对象,通过 setattr 的方式存在到 self.hook 中,名字就是被装饰方法的名字,值是刚刚创建的对象
  • 最后会在 names 的列表中把这个添加的方法的名字添加进去,判断一个 names 是否为空,如果为空,则抛出异常
  • 添加 add_hookspecs 的步骤全部完成

注册插件 register

注册插件 (register): 传递实现插件的实体类对象

  • 判断是否传递插件名字,如果没传,就获取对象的name属性,如果还没有就直接用 id() 生产一个随机字符串做当前对象在插件中的名字

  • 判断名字是否存在,或者是否已被注册: self._name2plugin 和 self._plugin2hookcaller,前者是用 plugin_name 做 key,后者是用 plugin object 做 key,判断是否已经注册过重复的 plugin

  • self._name2plugin[plugin_name(插件名字)] = plugin(传递的实体类对象)
    self._plugin2hookcallers[plugin(传递的实体类对象)] = hookcallers = [],其实就是初始化一下 self._plugin2hookcallers[plugin],因为列表的引用传递,所有直接修改 hookcallers 也可以作用在 self 中

  • 遍历实体类对象的方法列表,判断是否被 impl 装饰:

    • a.先获取到方法对象
    • b.判断对象是否是内置函数、函数、方法或者方法描述符,如果不是直接返回
    • c.获取该方法的属性 hook 对象创建时传递的名字 (myproject) + "_impl" ,没有则返回 None
    • d.判断获取到的值是不是 None 并且不是一个字典,则将获取到的 res 赋值为 None
    • e. 最后返回 res,其实就是 hookimpl 装饰器,如果你不给值就给一堆默认值
  • 先判断参数列表是否为空: 如果不为空,进行设置默认值 (其实正常是不会出现没有值的情况),然后从实体类对象中获取到该方法的对象

    • 在创建一个新的对象 (HookImpl):init(self,传递进来的实体类对象,hook 名字 (第一步获取或者 id 生成),method 对象,第四步返回的参数字典),并且将参数列表跟新到 self.dict
  • 判断 self.hook 中是否以及注册了当前插件 (就是 add_hookspecs 注册的 spec 中是否有当前方法)

    • 如果没有注册,会直接注册一个,这样注册 specmodule_or_class 参数会为空,意为着不会有额外的一些参数 eg:tryfirst
  • hook.has_spec() 判断注册 spec 的 spec 属性不为空

    • self._verify_hook(hook(spec 对象), hookimpl(插件对象)) a.先判断当前对象中是否有 (_call_history 属性),历史 和是否 需要使用 yield b. 判断 hookimpl 和 hook.spec 的参数列表是否相等,如果不相等报错
  • hook._maybe_apply_history(hookimpl)
    a.判断是否有_call_history 这个属性

  • hook._add_hookimpl(hookimpl):
    a.判断是否为 hookwrapper 为 True,添加到不同的 wrappers 中
    b.判断是否有 trylast tryfirst 属性,将 hookimpl 存放到对应位置
    c.将 hook 添加到 hookcallers 中

  • 遍历结束后,返回 plugin_name(第一步产生)

运行插件 pm.hook.myhook

运行插件 pm.hook.myhook(arg1=1, arg2=2):本质就是调用对象的call方法

  • 先判断是否有顺序参数,如果有直接报错
  • 在判断是否有_call_history 这个属性
  • 判断实际传入参数,是否和插件需要参数一样
  • self._hookexec(self(hook 对象), self.get_hookimpls()(全部的已经注册的插件), kwargs(传入的参数))

self._inner_hookexec(hook(hook 对象), methods(插件), kwargs(参数))

# 实际调用,也就是hook.multicall的方法
self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
            methods,
            kwargs,
            firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
        )
  • _multicall(hook_impls(插件), caller_kwargs(参数), firstresult=False(@hookspec传入,默认 False))

1. 先将 hook_impls 变成一个可迭代对象 (reversed(hook_impls))

2. 先把顺序参数的参数列表,拿到 (列表推导式)

3. 判断需不需要将其他插件执行的结果传递进去

- 需要
- 先从 hook_impl 中拿出对应的方法并且传递参数,执行关键字 yield 前面部分
- 然后 next()
- 最后将这个方法添加到 teardowns 列表中去

- 不需要
- 先从 hook_impl 中拿出对于的方法并且传递参数
- 判断执行后的返回值是不是为空,不为空则添加到 results 列表中
- 最后判断是否有 firstresult 属性,如果有直接结束循环

4. 最后执行 (finally 中代码)

- 如果 firstresult 为 true,那么直接返回第一个插件返回的结果即可
- 执行 teardowns 列表中的需要最后执行的插件
- 通过迭代器的 send 方法,将上几个插件的结果传递进去

5. 返回 result 对象 :会判断是否有报错 如果没有直接返回结果列表,如果有报错会抛出异常

以上就是Python测试框架pytest核心库pluggy详解的详细内容,更多关于Python pytest库pluggy的资料请关注三水点靠木其它相关文章!

Golang 相关文章推荐
一文读懂go中semaphore(信号量)源码
Apr 03 Golang
Golang 使用Map实现去重与set的功能操作
Apr 29 Golang
解决Golang中goroutine执行速度的问题
May 02 Golang
浅谈Golang 切片(slice)扩容机制的原理
Jun 09 Golang
go web 预防跨站脚本的实现方式
Jun 11 Golang
Golang并发操作中常见的读写锁详析
Aug 30 Golang
golang实现一个简单的websocket聊天室功能
Oct 05 Golang
Go语言并发编程 sync.Once
Oct 16 Golang
Golang中channel的原理解读(推荐)
Oct 16 Golang
Go归并排序算法的实现方法
Apr 06 Golang
Go语言安装并操作redis的go-redis库
Apr 14 Golang
GO中sync包自由控制并发示例详解
Aug 05 Golang
Go结合Gin导出Mysql数据到Excel表格
Aug 05 #Golang
GO中sync包自由控制并发示例详解
Aug 05 #Golang
Go语言编译原理之源码调试
Aug 05 #Golang
Go语言编译原理之变量捕获
Aug 05 #Golang
在ubuntu下安装go开发环境的全过程
Aug 05 #Golang
Go语言测试库testify使用学习
Jul 23 #Golang
Go语言怎么使用变长参数函数
Jul 15 #Golang
You might like
PHP实现带重试功能的curl连接示例
2016/07/28 PHP
PHP入门教程之PHP操作MySQL的方法分析
2016/09/11 PHP
Prototype的Class.create函数解析
2011/09/22 Javascript
JavaScript入门之事件、cookie、定时等
2011/10/21 Javascript
js取滚动条的尺寸的函数代码
2011/11/30 Javascript
解决JS中乘法的浮点错误的方法
2014/01/03 Javascript
jquery解析XML字符串和XML文件的方法说明
2014/02/21 Javascript
JavaScript生成的动态下雨背景效果实现方法
2015/02/25 Javascript
D3.js实现柱状图的方法详解
2016/09/21 Javascript
JavaScript中数据类型转换总结
2016/12/25 Javascript
微信小程序 弹窗自定义实例代码
2017/03/08 Javascript
jQuery插件imgAreaSelect基础讲解
2017/05/26 jQuery
Vue+webpack项目基础配置教程
2018/02/12 Javascript
轻松搞定jQuery+JSONP跨域请求的解决方案
2018/03/06 jQuery
jQuery插件jsonview展示json数据
2018/05/26 jQuery
微信小程序的授权实现过程解析
2019/08/02 Javascript
JS实现电商商品展示放大镜特效
2020/01/07 Javascript
[05:06]DOTA2-DPC中国联赛 正赛 VG vs Magma选手采访
2021/03/11 DOTA
解决python写的windows服务不能启动的问题
2014/04/15 Python
python调用百度语音识别api
2018/08/30 Python
pycharm运行程序时看不到任何结果显示的解决
2020/02/21 Python
Html5+CSS3+EL表达式问题小结
2020/12/19 HTML / CSS
白俄罗斯女装和针织品网上商店:Presli.by
2019/10/13 全球购物
Internal修饰符有什么含义
2013/07/10 面试题
军训生自我鉴定范文
2013/12/27 职场文书
乡下人家教学反思
2014/02/01 职场文书
2014年最新学习全国两会精神心得
2014/03/17 职场文书
关于读书的演讲稿
2014/05/07 职场文书
作风建设演讲稿
2014/05/23 职场文书
先进党支部申报材料
2014/12/24 职场文书
美术教师个人工作总结
2015/02/06 职场文书
中国汉字听写大会观后感
2015/06/02 职场文书
总经理年会致辞
2015/07/29 职场文书
利用python Pandas实现批量拆分Excel与合并Excel
2021/05/23 Python
详解Python类和对象内容
2021/06/22 Python
Python+腾讯云服务器实现每日自动健康打卡
2021/12/06 Python