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 相关文章推荐
Golang 实现超大文件读取的两种方法
Apr 27 Golang
Golang中异常处理机制详解
Jun 08 Golang
Golang中channel的原理解读(推荐)
Oct 16 Golang
Go语言基础map用法及示例详解
Nov 17 Golang
一文搞懂Golang 时间和日期相关函数
Dec 06 Golang
golang实现浏览器导出excel文件功能
Mar 25 Golang
golang生成vcf通讯录格式文件详情
Mar 25 Golang
golang连接MySQl使用sqlx库
Apr 14 Golang
详解Go语言中Get/Post请求测试
Jun 01 Golang
Go调用Rust方法及外部函数接口前置
Jun 14 Golang
Go结合Gin导出Mysql数据到Excel表格
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
回帖脱衣服的图片实现代码
2014/02/15 PHP
php保存信息到当前Session的方法
2015/03/16 PHP
PHP小白必须要知道的php基础知识(超实用)
2017/10/10 PHP
PHP Class SoapClient not found解决方法
2018/01/20 PHP
ThinkPHP框架实现的微信支付接口开发完整示例
2019/04/10 PHP
PHP使用phpunit进行单元测试示例
2019/09/23 PHP
php常用的工具开发整理
2019/09/26 PHP
js AspxButton的客户端操作
2009/06/26 Javascript
Javascript select下拉框操作常用方法
2009/11/09 Javascript
jqgrid 表格数据导出实例
2013/11/21 Javascript
谷歌地图打不开的解决办法
2014/08/07 Javascript
jQuery中each()、find()和filter()等节点操作方法详解(推荐)
2016/05/25 Javascript
Vue.js每天必学之计算属性computed与$watch
2016/09/05 Javascript
深入学习js瀑布流布局
2016/10/14 Javascript
JS实现含有中文字符串的友好截取功能分析
2017/03/13 Javascript
JavaScript数组和对象的复制
2017/03/21 Javascript
详解Vue2中组件间通信的解决全方案
2017/07/28 Javascript
Webpack框架核心概念(知识点整理)
2017/12/22 Javascript
AngularJS动态添加数据并删除的实例
2018/02/27 Javascript
JS定义函数的几种常用方法小结
2019/05/23 Javascript
vue3.0中的双向数据绑定方法及优缺点
2019/08/01 Javascript
[01:01:52]DOTA2-DPC中国联赛定级赛 SAG vs iG BO3第二场 1月9日
2021/03/11 DOTA
Python subprocess模块学习总结
2014/03/13 Python
Python中os和shutil模块实用方法集锦
2014/05/13 Python
python通过文件头判断文件类型
2015/10/30 Python
Python星号*与**用法分析
2018/02/02 Python
Tensorflow 训练自己的数据集将数据直接导入到内存
2018/06/19 Python
virtualenv介绍及简明教程
2020/06/23 Python
英国创新设计文具、卡片和礼品包装网站:Paperchase
2018/07/14 全球购物
高中三年学习生活的自我评价
2013/10/10 职场文书
安全目标责任书
2014/07/22 职场文书
二审代理词范文
2015/05/25 职场文书
《自己的花是让别人看的》教学反思
2016/02/19 职场文书
股东出资协议书
2016/03/21 职场文书
python实现网络五子棋
2021/04/11 Python
Linux中sftp常用命令整理
2022/06/28 Servers