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 Int转string几种方式性能测试
Apr 28 Golang
解决golang在import自己的包报错的问题
Apr 29 Golang
golang slice元素去重操作
Apr 30 Golang
go select编译期的优化处理逻辑使用场景分析
Jun 28 Golang
手把手教你导入Go语言第三方库
Aug 04 Golang
golang实现浏览器导出excel文件功能
Mar 25 Golang
Go并发4种方法简明讲解
Apr 06 Golang
Golang数据类型和相互转换
Apr 12 Golang
Go获取两个时区的时间差
Apr 20 Golang
基于Python实现西西成语接龙小助手
Aug 05 Golang
Go中使用gjson来操作JSON数据的实现
Aug 14 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 ios推送(代码)
2013/07/01 PHP
使用PHPExcel实现数据批量导出为excel表格的方法(必看)
2017/06/09 PHP
总结PHP代码规范、流程规范、git规范
2018/06/18 PHP
laravel5.1框架model类查询的实现方法
2019/10/08 PHP
Aster vs Newbee BO5 第三场2.19
2021/03/10 DOTA
JS控制表单提交的方法
2015/07/09 Javascript
js密码强度校验
2015/11/10 Javascript
浅谈JS读取DOM对象(标签)的自定义属性
2016/11/21 Javascript
javascript prototype原型详解(比较基础)
2016/12/26 Javascript
使用Bootstrap + Vue.js实现添加删除数据示例
2017/02/27 Javascript
Angularjs使用指令做表单校验的方法
2017/03/31 Javascript
微信小程序用户位置权限的获取方法(拒绝后提醒)
2018/11/15 Javascript
解决layer.open后laydate失效的问题
2019/09/06 Javascript
python实现的各种排序算法代码
2013/03/04 Python
python中__call__方法示例分析
2014/10/11 Python
Python中的命令行参数解析工具之docopt详解
2017/03/27 Python
APIStar:一个专为Python3设计的API框架
2018/09/26 Python
Python3.5基础之函数的定义与使用实例详解【参数、作用域、递归、重载等】
2019/04/26 Python
在python中,使用scatter绘制散点图的实例
2019/07/03 Python
Python爬虫实现使用beautifulSoup4爬取名言网功能案例
2019/09/15 Python
python logging添加filter教程
2019/12/24 Python
python计算波峰波谷值的方法(极值点)
2020/02/18 Python
Python paramiko 模块浅谈与SSH主要功能模拟解析
2020/02/29 Python
python实现数学模型(插值、拟合和微分方程)
2020/11/13 Python
Harman Audio官方商店:购买JBL、Harman Kardon、Infinity和AKG
2019/12/05 全球购物
自我鉴定200字
2013/10/28 职场文书
会展策划与管理专业大学生职业生涯规划
2014/02/07 职场文书
计算机专业职业规划
2014/02/28 职场文书
安全技术说明书
2014/05/09 职场文书
2015新年寄语大全
2014/12/08 职场文书
工作迟到检讨书范文
2015/05/06 职场文书
2015暑期社会实践调查报告
2015/07/14 职场文书
大学入学感言
2015/08/01 职场文书
什么是检讨书?检讨书的格式及范文
2019/11/05 职场文书
python四种出行路线规划的实现
2021/06/23 Python
python中 Flask Web 表单的使用方法
2022/05/20 Python