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

经过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

  总结:

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

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

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Esxi6.0 安装web管理界面

安装6.0之后默认是vSphere Client进行远程连接,需要安装客户端,不是太方便。搜索发现还真可以实现web管理,步骤如下: 1、开启esxi的ssh,步骤如下图: 2、下载升级包esxui-signed-7119706.vib,上…

Linux 环境下,jdbc连接mysql问题

1. 下载MySQL的JDBC驱动: 从MySQL官网下载最新的MySQL Connector/J,并将其解压到某个目录,比如/usr/local/mysql/。 2. 将JDBC驱动添加到类路径: 将JDBC驱动添加到类路径,可以使用以下命令: export CLA…

gmid方法设计五管OTA二级远放

首先给出第一级是OTA,第二级是CS的二级运放电路图: gmid的设计方法可以根据GBW、Av、CL来进行电路设计,因此在设计电路之前需要以上的参数要求。 1、为了满足电路的相位裕度至少60,需要对GBW、主极点、零点进行分析。 首先给出其…

解决npm install时报:gyp ERR! configure error

报错内容: npm ERR! gyp ERR! cwd C:\Users\zccbbg\code\my\examvue\node_modules\node-sass npm ERR! gyp ERR! node -v v16.13.1 npm ERR! gyp ERR! node-gyp -v v3.8.0 npm ERR! gyp ERR! not ok npm ERR! Build failed with error code: 1 解决办法:…

Jmeter接口测试 —— jmeter对图片验证码的处理

jmeter对图片验证码的处理 在web端的登录接口经常会有图片验证码的输入,而且每次登录时图片验证码都是随机的;当通过jmeter做接口登录的时候要对图片验证码进行识别出图片中的字段,然后再登录接口中使用; 通过jmeter对图片验证码…

一对一聊天

1.创建包 1.服务界面 package yiduiy;import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.uti…

OkGo导入失败解决办法

jcenter()maven { url "https://jitpack.io" }再同步就可以了

LeetCode Hot100 207.课程表

题目: 你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习…

docker:安装mysql以及最佳实践

文章目录 1、拉取镜像2、运行容器3、进入容器方式一方式二方式三容器进入后连接mysql和在宿主机连接mysql的区别 持久化数据持久化数据最佳实践 1、拉取镜像 docker pull mysql2、运行容器 docker run -d -p 3307:3306 --name mysql-container -e MYSQL_ROOT_PASSWORD123456 …

antdesign前端一直加载不出来

antdesign前端一直加载不出来 报错:Module “./querystring” does not exist in container. while loading “./querystring” from webpack/container/reference/mf at mf-va_remoteEntry.js:751:11 解决方案:Error: Module “xxx“ does not exist …

分布式锁常见实现方案

分布式锁常见实现方案 基于 Redis 实现分布式锁 如何基于 Redis 实现一个最简易的分布式锁? 不论是本地锁还是分布式锁,核心都在于“互斥”。 在 Redis 中, SETNX 命令是可以帮助我们实现互斥。SETNX 即 SET if Not eXists (对应 Java 中…

【开源】基于Vue+SpringBoot的用户画像活动推荐系统

项目编号: S 061 ,文末获取源码。 \color{red}{项目编号:S061,文末获取源码。} 项目编号:S061,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 兴趣标签模块2.3 活…

马尔科夫决策过程(Markov Decision Process)揭秘

RL基本框架、MDP概念 MDP是强化学习的基础。MDP能建模一系列真实世界的问题,它在形式上描述了强化学习的框架。RL的交互过程就是通过MDP表示的。RL中Agent对Environment做出一个动作(Action),Environment给Agent一个反馈&#xff…

Ubuntu安装过程记录

软件准备 硬件 Acer电脑,AMD a6-440m芯片 64g优盘一个,实际就用了不到5g。 Ubuntu :官网 下载Ubuntu桌面系统 | Ubuntu 下载桌面版Ubuntu 22.04.3 LTS LTS属于稳定版 u盘系统盘制作软件 Rufus :Rufus - 轻松创建 USB 启动…

【编程基础心法】「创建模式系列」让我们一起来学编程界的“兵法”设计模式(工厂模式)

【编程基础心法】「创建模式系列」让我们一起来学编程界的“兵法”设计模式(工厂模式) 设计模式之间的千丝万缕工厂模式简单工厂方法简单工厂定义多方法模式多个静态方法模式简单工厂模式的问题 工厂方法模式定义工厂抽象接口工厂方法存在的问题 抽象工厂…

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

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

高防CDN可以更好的防御网站被攻击

高防CDN是在原服务器的基础上配置了DDoS高防、 CC防护、CDN加速来确保线上业务安全快速地运行。使用高防CDN后网站服务器会被隐藏在后端,使攻击者无法攻击到网站服务器,只能攻击部署在前端的CDN节点,每当检测到是攻击流量的时候还会自动对其进…

对比分析:黑盒测试 VS 白盒测试

一、引言 在软件开发过程中,测试是确保产品质量的关键环节。其中,黑盒测试和白盒测试是两种常见的测试方法。本文将详细解析这两种测试方法的定义、特点,同时通过具体示例进行对比分析。 二、黑盒测试 黑盒测试,又称功能测试&…

SpaceSight、Echo 联合升级,打造更懂场景的 AI 「超级门店」

当各领域都在谈论「增长」,门店业务的增长又该从哪里开始着手…… 在日常运营中,「高效」和「细致」是否无法同时实现?「任务下达」和「任务执行」之间有多大偏差? 在客户洞察上,如何用「过去」的数据预测「未来」&…

Spring Cloud Stream 4.0.4 rabbitmq 发送消息多function

使用 idea 创建 Springboot 项目 添加 Spring cloud stream 和 rabbitmq 依赖 pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchem…