阿里大佬讲解的接口自动化测试框架pytest系列——pluggy插件源码解读:hook钩子函数调用执行过程分析

news2025/1/24 10:35:50

经过pluggy源码解读系列1-4的分析,已经完成插件定义、spec定义,插件注册等环节,下面就到了调用插件执行了,即hook钩子函数是如何被调用执行的,下面还是先把pluggy使用的代码放下面:

import pluggy

# HookspecMarker 和 HookimplMarker 实质上是一个装饰器带参数的装饰器类,作用是给函数增加额外的属性设置
hookspec = pluggy.HookspecMarker("myproject")
hookimpl = pluggy.HookimplMarker("myproject")

# 定义自己的Spec,这里可以理解为定义接口类
class MySpec:
    # hookspec 是一个装饰类中的方法的装饰器,为此方法增额外的属性设置,这里myhook可以理解为定义了一个接口
    @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
    def myhook(self, arg1, arg2):
        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)

通过上面的例子,可以看出,最后一个步骤就是通过PluginManager实例的pm的一个hook属性调用myhook函数,而myhook即定义的接口函数,在这个例子中,这个接口函数在pluggin_1和pluggin_2两个插件中都有实现,则这里两个插件的myhook函数都会执行,执行的顺序也是后讲究的,那么这些流程的控制执行等都本节详细讲述

现在先回头再看一下,在分析add_hookspecs方法的时候讲到,首先hook是PluginManager类的一个实例,这个比较好理解,下面是add_hookspecs方法的源代码,这个在前面都已经详细的分析过了,这里放这里再简单回顾一下,通过下面的代码可以发现,就是在这个函数中给hook设置了接口函数myhook的属性,myhook的属性值是_HookCaller类的一个实例,那么这里一个实例为什么当做函数调用了呢,这就涉及到python的高级语法中call魔法函数的应用了

def add_hookspecs(self, module_or_class):
    """ add new hook specifications defined in the given ``module_or_class``.
    Functions are recognized if they have been decorated accordingly. """
    names = []
    for name in dir(module_or_class):
        spec_opts = self.parse_hookspec_opts(module_or_class, name)
        if spec_opts is not None:
            hc = getattr(self.hook, name, None)
            if hc is None:
                hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts)
                setattr(self.hook, name, hc)
            else:
                # plugins registered this hook without knowing the spec
                hc.set_specification(module_or_class, spec_opts)
                for hookfunction in hc.get_hookimpls():
                    self._verify_hook(hc, hookfunction)
            names.append(name)

    if not names:
        raise ValueError(
            "did not find any %r hooks in %r" % (self.project_name, module_or_class)
        )

前面也都分析过call的应用,所以这里就是应用了这个特点,即把_HookCaller类的一个实例当做函数调用,实质上就是调用了_HookCaller类的call魔法函数,这里把_HookCaller类的call方法的代码放到下面,前面层提过,这个方法是整个pluggy最最核心的一个函数(pluggy最最核心的类是PluginManager类,它是插件管理注册等等控制类,而pluggy最最核心的函数就是_HookCaller类的call函数了,它控制了整个插件系统的钩子函数的执行过程)

def __call__(self, *args, **kwargs):
    if args:
        raise TypeError("hook calling supports only keyword arguments")
    assert not self.is_historic()

    # This is written to avoid expensive operations when not needed.
    if self.spec:
        for argname in self.spec.argnames:
            if argname not in kwargs:
                notincall = tuple(set(self.spec.argnames) - kwargs.keys())
                warnings.warn(
                    "Argument(s) {} which are declared in the hookspec "
                    "can not be found in this hook call".format(notincall),
                    stacklevel=2,
                )
                break

        firstresult = self.spec.opts.get("firstresult")
    else:
        firstresult = False

    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)

下面就对这个函数做详细的分析

  • 首先这个函数的前两行就限定了插件中定义的函数的参数必须是key-value键值对的形式,不支持可变参数的形式
  • 然后就是对参数做分析,主要就是分析出firstresult的值是True还是False
  • 下面就是调用self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)函数了这里,首先name就是接口函数的名字,比如这里就是myhook字符串

下面看下第二个参数,第二个参数是一个函数,这个函数的代码如下:这里可以看出,这里就是上一节分析的注册函数的列表,所以这个返回的是一个实现函数的列表,第三个参数是函数的参数,第四个参数就是firstresult值

def get_hookimpls(self):
    # Order is important for _hookexec
    return self._nonwrappers + self._wrappers

下面就到了最最核心的函数了,即hookexec函数,通过前面几节的分析,已经知道这个函数就是callers.py文件中的_multicall函数,代码如下:

def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):
    """Execute a call into multiple python functions/methods and return the
    result(s).

    ``caller_kwargs`` comes from _HookCaller.__call__().
    """
    __tracebackhide__ = True
    results = []
    excinfo = None
    try:  # run impl and wrapper setup functions in a loop
        teardowns = []
        try:
            for hook_impl in reversed(hook_impls):
                try:
                    args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                except KeyError:
                    for argname in hook_impl.argnames:
                        if argname not in caller_kwargs:
                            raise HookCallError(
                                "hook call must provide argument %r" % (argname,)
                            )

                if hook_impl.hookwrapper:
                    try:
                        gen = hook_impl.function(*args)
                        next(gen)  # first yield
                        teardowns.append(gen)
                    except StopIteration:
                        _raise_wrapfail(gen, "did not yield")
                else:
                    res = hook_impl.function(*args)
                    if res is not None:
                        results.append(res)
                        if firstresult:  # halt further impl calls
                            break
        except BaseException:
            excinfo = sys.exc_info()
    finally:
        if firstresult:  # first result hooks return a single value
            outcome = _Result(results[0] if results else None, excinfo)
        else:
            outcome = _Result(results, excinfo)

        # run all wrapper post-yield blocks
        for gen in reversed(teardowns):
            try:
                gen.send(outcome)
                _raise_wrapfail(gen, "has second yield")
            except StopIteration:
                pass

        return outcome.get_result()

这个最核心的函数,其实也是比较容易看懂的,只要前几节的分析大概都还有个印象,那么这个函数还是比较容易理解的

首先定义个一个结果列表,用于存放每个插件的实现函数执行的结果

然后定义了个teardown的列表,用于存放执行teardown操作的操作对象

然后将hook_impls即插件中对接口函数的实现函数倒序遍历,这也是看很多文档博客会说pluggy插件执行的顺序是后注册先执行的原因,然后开始解析函数的参数

然后判断实现函数的hookwrapper属性值是否为True,如果为True表示此函数带有yield关键字,即首先执行yield之前的代码,然后会生成一个对象,即生成器,将生成的对象存放teardowns,用于所有插件之后再来执行这些操作,这也就是为什么网上很多博客等说的pluggy的插件实现函数中如果带有yield,则yield之后的代码会在所有的普通插件执行完成之后再去执行。else分支就是不带yield关键字的实现函数,则执行执行,并且将结果存放到results列表,同时如果判断firstresult结果为True,则结束循环,即执行得到一个结果即OK

当然如果firstresult为False,则所有的插件注册的函数都会执行的

在finnally代码块中可以看到,如果firstresult结果为True,则直接返回第一个结果,而如果firstresult为False,则会讲所有的结果以列表的形式返回

最后再去倒序遍历执行teardown列表中存放的操作,即当带有多个yield关键字插件的时候,后注册的yeild之后的代码先执行

最后将结果返回

ok,pluggy的钩子函数的执行过程的源码分析就到这里了

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

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

  总结:

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

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1289674.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PIKA,一个神奇的AI工具

随着人工智能技术的不断发展,越来越多的创新性工具开始涌现,为各行各业带来了巨大的变革。其中,视频生成AI工具PIKA,以其独特的功能和广泛的应用领域,吸引了众多用户的关注。本文将详细介绍PIKA的功能、特点以及应用前…

PyQt6 QGroupBox分组框控件

​锋哥原创的PyQt6视频教程: 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计37条视频,包括:2024版 PyQt6 Python桌面开发 视频教程(无废话…

大话数据结构-查找-有序表查找

注:本文同步发布于稀土掘金。 3 有序表查找 3.1 折半查找 折半查找(Binary Search)技术,又称为二分查找,它的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须…

【Vue3从入门到项目实现】RuoYi-Vue3若依框架前端学习——登录页面

若依官方的前后端分离版中,前端用的Vue2,这个有人改了Vue3的前端出来。刚好用来学习: https://gitee.com/weifengze/RuoYi-Vue3 运行前后端项目 首先运行项目 启动前端,npm install、npm run dev 启动后端,按教程配置…

信息系统安全运维服务资质认证申报流程详解

随着我国信息化和信息安全保障工作的不断深入,以应急处理、风险评估、灾难恢复、系统测评、安全运维、安全审计、安全培训和安全咨询等为主要内容的信息安全服务在信息安全保障中的作用日益突出。加强和规范信息安全服务资质管理已成为信息安全管理的重要基础性工作…

nodeJS爬虫-爬取虎嗅新闻

1.安装依赖库到本地,需要的库有:安装方法见Node.js笔记说明 const superagent require(superagent); const cheerio require(cheerio); const async require(async); const fs require(fs); const url require(url); const request require(reques…

Apache Doris 详细教程(一)

1、Doris简介 1.1、doris概述 Apache Doris 由百度大数据部研发(之前叫百度 Palo,2018 年贡献到 Apache 社区后, 更名为 Doris ),在百度内部,有超过 200 个产品线在使用,部署机器超过 1000 台…

用OpenCV与MFC写一个图像格式转换及简单处理程序

打开不同格式的图形文件,彩色装灰度图像、锐化、高斯滤波、边界检测及将其存储为需求格式是图像处理的最基本的操作。如果单纯用MFC编程,是一个令人头痛的事情,有不少的代码量。可用OpenCV与MFC编程就变得相对简单。下面来详细演示这一编程操…

股票所有均线都跌破应该怎么操作?

股票跌破所有均线说明股票趋势是走坏的,并且均线对股票起到压制作用,投资者有两种操作方式,第一种是割肉换股,投资者可以在股票小幅上涨时,将股票全部卖出,再买入最近比较强势的个股,赚取收益、…

ubuntu安装MySQL8

1.下载mysql8 MySQL :: Download MySQL Installer (Archived Versions) 选择对应的mysql版本和对应的ubuntu版本图即可 2.下载后上传到sftp文件夹中,然后通过以下命令解压 tar -xvf mysql-server_8.0.29-1ubuntu20.04_amd64.deb-bundle.tar 3.依次安装即可 &#…

数据结构与算法之美学习笔记:32 | 字符串匹配基础(上):如何借助哈希算法实现高效字符串匹配?

标题 前言BF 算法RK 算法解答开篇 & 内容小结 前言 本节课程思维导图: 从今天开始,我们来学习字符串匹配算法。我们用的最多的比如 Java 中的 indexOf(),Python 中的 find() 函数等,它们底层就是依赖接下来要讲的字符串匹配算…

解决:spring boot+mybatis进行增删改查的时候,接收到前端数据,并且执行成功了,但是数据库里面添加的内容都是空值

在写spring boot整合mybatis的时候,我在Apifox里面测试,数据也传递过去了,后端服务器也接收到了参数,就是数据库里面添加的都是空值??? 前端接收到了数据,并且没有报错 Apifox里面也…

【微服务】spring循环依赖深度解析

目录 一、循环依赖概述 1.2 spring中的循环依赖 二、循环依赖问题模拟 2.1 循环依赖代码演示 2.2 问题分析与解决 2.2.1 使用反射中间容器 三、spring循环依赖问题解析 3.1 spring中的依赖注入 3.1.1 field属性注入 3.1.2 setter方法注入 3.1.3 构造器注入 3.2 spri…

Python中字符串列表的相互转换详解

更多资料获取 📚 个人网站:ipengtao.com 在Python编程中,经常会遇到需要将字符串列表相互转换的情况。这涉及到将逗号分隔的字符串转换为列表,或者将列表中的元素连接成一个字符串。本文将深入讨论这些情景,并提供丰富…

JAVA网络编程——BIO、NIO、AIO深度解析

I/O 一直是很多Java同学难以理解的一个知识点,这篇帖子将会从底层原理上带你理解I/O,让你看清I/O相关问题的本质。 1、I/O的概念 I/O 的全称是Input/Output。虽常谈及I/O,但想必你也一时不能给出一个完整的定义。搜索了谷哥欠,发…

吉利护航,宣称比友商“更懂车”,魅族造车的底气与底色

继小米、华为后,又一家手机厂商宣布跨界造车。 在近日举办的2023魅族秋季无界生态发布会上,星纪魅族集团(下称“魅族”)董事长兼CEO沈子瑜宣布,魅族正式进入汽车市场,将在2024年第一季度启动“DreamCar共创…

CoreDNS实战(十一)-分流与重定向

本文主要介绍了目前CoreDNS服务在外部域名递归结果过程中出现的一些问题以及使用dnsredir插件进行分流和alternate插件进行重试优化的操作。 1 自建DNS服务现状 一般来说,无论是bind9、coredns、dnsmasq、pdns哪类dns服务器,我们自建的监听在UDP53端口…

微信怎么自动跟圈?怎么一键转发好友的朋友圈?

做私域、微商的小伙伴们每天需要发许多朋友圈来推广产品,一个人手上就有好几个微信的话,每个微信都要发朋友圈的话,非常麻烦。有没有一键转发同步好友朋友圈的功能呢? 朋友圈互动 1)查看朋友圈:可通过昵称…

智能优化算法应用:基于梯度算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于梯度算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于梯度算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.梯度算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

识别和修复网站上损坏链接的最佳实践

如果您有一个网站,我们知道您花了很多时间在它上面,以使其成为最好的资源。如果你的链接不起作用,你的努力可能是徒劳的。您网站上的断开链接可能会以两种方式损害您的业务: 它们对企业来说是可怕的,因为当消费者点击…