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 生成对应的数据表struct定义操作
Apr 28 Golang
使用Golang的channel交叉打印两个数组的操作
Apr 29 Golang
go语言中GOPATH GOROOT的作用和设置方式
May 05 Golang
Golang: 内建容器的用法
May 05 Golang
K8s部署发布Golang应用程序的实现方法
Jul 16 Golang
如何利用golang运用mysql数据库
Mar 13 Golang
详解Golang如何优雅的终止一个服务
Mar 21 Golang
Go语言特点及基本数据类型使用详解
Mar 21 Golang
Go语言 详解net的tcp服务
Apr 14 Golang
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
Jun 21 Golang
Go微服务项目配置文件的定义和读取示例详解
Jun 21 Golang
Go语言编译原理之变量捕获
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
自动跳转中英文页面
2006/10/09 PHP
IP攻击升级,程序改进以对付新的攻击
2010/11/23 PHP
ThinkPHP登录功能的实现方法
2014/08/20 PHP
举例详解PHP脚本的测试方法
2015/08/05 PHP
PHP实现简易blog的制作
2016/10/24 PHP
PHP实现浏览器格式化显示XML的方法示例
2019/01/22 PHP
php获取微信openid方法总结
2019/10/10 PHP
Jquery实现视频播放页面的关灯开灯效果
2013/05/27 Javascript
详解JS面向对象编程
2016/01/24 Javascript
AngularJS中实现用户访问的身份认证和表单验证功能
2016/04/21 Javascript
浅谈Node.js轻量级Web框架Express4.x使用指南
2017/05/03 Javascript
jquery实现侧边栏左右伸缩效果的示例
2017/12/19 jQuery
安装Node.js并启动本地服务的操作教程
2018/05/12 Javascript
Vue中import from的来源及省略后缀与加载文件夹问题
2020/02/09 Javascript
Electron整合React使用搭建开发环境的步骤详解
2020/06/07 Javascript
JS使用setInterval计时器实现挑战10秒
2020/11/08 Javascript
跟老齐学Python之从格式化表达式到方法
2014/09/28 Python
Python函数返回值实例分析
2015/06/08 Python
Python中的anydbm模版和shelve模版使用指南
2015/07/09 Python
python实现决策树C4.5算法详解(在ID3基础上改进)
2017/05/31 Python
Python3多线程爬虫实例讲解代码
2018/01/05 Python
Django项目中使用JWT的实现代码
2019/11/04 Python
Python帮你识破双11的套路
2019/11/11 Python
django 多数据库及分库实现方式
2020/04/01 Python
jupyter notebook 增加kernel教程
2020/04/10 Python
俄罗斯最大的在线珠宝大卖场:Nebo
2019/12/08 全球购物
给女朋友的道歉信
2014/01/10 职场文书
打架检讨书400字
2014/01/17 职场文书
简洁的英文求职信范文
2014/05/03 职场文书
汉语言文学专业求职信
2014/06/19 职场文书
党员三严三实对照检查材料
2014/10/13 职场文书
公司员工安全协议书
2014/11/21 职场文书
英语辞职信范文
2015/02/28 职场文书
学校后勤工作总结2015
2015/05/15 职场文书
预备党员考察意见范文
2015/06/01 职场文书
2019最新版股权转让及委托持股协议书范本
2019/08/07 职场文书