0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

python学习:三个测试库的装饰器实现思路

454398 来源:Python猫公众号 作者:豌豆花下猫 2020-09-27 11:44 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

Python 中实现参数化测试的几个库,并留下一个问题:

它们是如何做到把一个方法变成多个方法,并且将每个方法与相应的参数绑定起来的呢?

我们再提炼一下,原问题等于是:在一个类中,如何使用装饰器把一个类方法变成多个类方法(或者产生类似的效果)?

# 带有一个方法的测试类
class TestClass:
    def test_func(self):
        pass

# 使用装饰器,生成多个类方法
class TestClass:
    def test_func1(self):
        pass
    def test_func2(self):
        pass
    def test_func3(self):
        pass

Python 中装饰器的本质就是移花接木,用一个新的方法来替代被装饰的方法。在实现参数化的过程中,我们介绍过的几个库到底用了什么手段/秘密武器呢?

1、ddt 如何实现参数化?

先回顾一下上篇文章中 ddt 库的写法:

import unittest
from ddt import ddt,data,unpack
@ddt
class MyTest(unittest.TestCase):
    @data((3, 1), (-1, 0), (1.2, 1.0))
    @unpack
    def test(self, first, second):
        pass

ddt 可提供 4 个装饰器:1 个加在类上的 @ddt,还有 3 个加在类方法上的 @data、@unpack 和 @file_data(前文未提及)。

先看看加在类方法上的三个装饰器的作用:

# ddt 版本(win):1.2.1
def data(*values):
    global index_len
    index_len = len(str(len(values)))
    return idata(values)

def idata(iterable):
    def wrapper(func):
        setattr(func, DATA_ATTR, iterable)
        return func
    return wrapper

def unpack(func):
    setattr(func, UNPACK_ATTR, True)
    return func

def file_data(value):
    def wrapper(func):
        setattr(func, FILE_ATTR, value)
        return func
    return wrapper

它们的共同作用是在类方法上 setattr() 添加属性。至于这些属性在什么时候使用?下面看看加在类上的 @ddt 装饰器源码:

第一层 for 循环遍历了所有的类方法,然后是 if/elif 两条分支,分别对应 DATA_ATTR/FILE_ATTR,即对应参数的两种来源:数据(@data)和文件(@file_data)。

elif 分支有解析文件的逻辑,之后跟处理数据相似,所以我们把它略过,主要看前面的 if 分支。这部分的逻辑很清晰,主要完成的任务如下:
• 遍历类方法的参数键值对
• 根据原方法及参数对,创建新的方法名
• 获取原方法的文档字符串
• 对元组和列表类型的参数作解包
• 在测试类上添加新的测试方法,并绑定参数与文档字符串

分析源码,可以看出,@data、@unpack 和 @file_data 这三个装饰器主要是设置属性并传参,而 @ddt 装饰器才是核心的处理逻辑。

这种将装饰器分散(分别加在类与类方法上),再组合使用的方案,很不优雅。为什么就不能统一起来使用呢?后面我们会分析它的难言之隐,先按下不表,看看其它的实现方案是怎样的?

2、parameterized 如何实现参数化?

先回顾一下上篇文章中 parameterized 库的写法:

import unittest
from parameterized import parameterized
class MyTest(unittest.TestCase):
    @parameterized.expand([(3,1), (-1,0), (1.5,1.0)])
    def test_values(self, first, second):
        self.assertTrue(first > second)

它提供了一个装饰器类 @parameterized,源码如下(版本 0.7.1),主要做了一些初始的校验和参数解析,并非我们关注的重点,略过。

我们主要关注这个装饰器类的 expand() 方法,它的文档注释中写到:

A "brute force" method of parameterizing test cases. Creates new test cases and injects them into the namespace that the wrapped function is being defined in. Useful for parameterizing tests in subclasses of 'UnitTest', where Nose test generators don't work.

关键的两个动作是:“creates new test cases(创建新的测试单元)”和“inject them into the namespace…(注入到原方法的命名空间)”。

关于第一点,它跟 ddt 是相似的,只是一些命名风格上的差异,以及参数的解析及绑定不同,不值得太关注。

最不同的则是,怎么令新的测试方法生效?

parameterized 使用的是一种“注入”的方式:

inspect 是个功能强大的标准库,在此用于获取程序调用栈的信息。前三句代码的目的是取出 f_locals,它的含义是“local namespace seen by this frame”,此处 f_locals 指的就是类的局部命名空间。

说到局部命名空间,你可能会想到 locals(),但是,我们之前有文章提到过“locals() 与 globals() 的读写问题”,locals() 是可读不可写的,所以这段代码才用了 f_locals。

3、pytest 如何实现参数化?

按惯例先看看上篇文章中的写法:

import pytest
@pytest.mark.parametrize("first,second", [(3,1), (-1,0), (1.5,1.0)])
def test_values(first, second):
    assert(first > second)

首先看到“mark”,pytest 里内置了一些标签,例如 parametrize、timeout、skipif、xfail、tryfirst、trylast 等,还支持用户自定义的标签,可以设置执行条件、分组筛选执行,以及修改原测试行为等等。

用法也是非常简单的,然而,其源码可复杂多了。我们这里只关注 parametrize,先看看核心的一段代码:

根据传入的参数对,它复制了原测试方法的调用信息,存入待调用的列表里。跟前面分析的两个库不同,它并没有在此创建新的测试方法,而是复用了已有的方法。在 parametrize() 所属的 Metafunc 类往上查找,可以追踪到 _calls 列表的使用位置:

最终是在 Function 类中执行:

好玩的是,在这里我们可以看到几行神注释……

阅读(粗浅涉猎) pytest 的源码,真的是自讨苦吃……不过,依稀大致可以看出,它在实现参数化时,使用的是生成器的方案,遍历一个参数则调用一次测试方法,而前面的 ddt 和 parameterized 则是一次性把所有参数解析完,生成 n 个新的测试方法,再交给测试框架去调度。

对比一下,前两个库的思路很清晰,而且由于其设计单纯是为了实现参数化,不像 pytest 有什么标记和过多的抽象设计,所以更易读易懂。前两个库发挥了 Python 的动态特性,设置类属性或者注入局部命名空间,而 pytest 倒像是从什么静态语言中借鉴的思路,略显笨拙。

4、最后小结

回到标题中的问题“如何将一个方法变为多个方法?”除了在参数化测试中,不知还有哪些场景会有此诉求?欢迎留言讨论。

本文分析了三个测试库的装饰器实现思路,通过阅读源码,我们可以发现它们各有千秋,这个发现本身还挺有意思。在使用装饰器时,表面看它们差异不大,但是真功夫的细节都隐藏在底下。

源码分析的意义在于探究其所以然,在这次探究之旅中,读者们可有什么收获啊?

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Function
    +关注

    关注

    0

    文章

    14

    浏览量

    10341
  • python
    +关注

    关注

    59

    文章

    4891

    浏览量

    90388
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    强强联合:imc FAMOS内嵌Python接口,提高分析效率!

    德国知名车厂工程师首选将Python和imcFAMOS结合起来用于分析测试和测量数据——这是否有意义?事实上这非常有意义。Python和imcFAMOS可以很好地相互补充。Python
    的头像 发表于 03-18 09:05 539次阅读
    强强联合:imc FAMOS内嵌<b class='flag-5'>Python</b>接口,提高分析效率!

    导热系数越高越好?关于导热硅胶片的三个认知误区

    。本文将揭示三个最常见的认知偏差,帮助您做出更明智的选择。 误区一:导热系数越高,散热效果就一定越好 这可能是最具迷惑性的误区。理论上,导热系数越高,材料传导热量的能力越强。然而在实际应用中,散热
    发表于 03-12 13:49

    如何在 Vision Five 2 上安装 python

    这可能是一完全愚蠢的问题,但我如何在 Vision Five 2 上安装 python 。 使用该命令后,它给了我这个错误。 默认为用户安装,因为普通站点包不可写 错误:找不到满足要求
    发表于 03-06 07:51

    无法去除 Python VisionFive.i2c 的终端输出?

    烧的官方最新八月份的 debian 12 的系统。 根据这个案例安装好了 python 环境和 VisionFive 。 执行下面这条代码: import VisionFive.i2c
    发表于 02-25 06:13

    骏马奔腾,芯向未来:SiC功率器件的“三个必然”与丙午马年的产业跃迁

    骏马奔腾,芯向未来:SiC功率器件的“三个必然”与丙午马年的产业跃迁 日期: 2026年 丙午马年 除夕 主题: 倾佳电子杨茜“三个必然”战略论断与基本半导体碳化硅技术的产业替代逻辑 关键词
    的头像 发表于 02-15 10:33 195次阅读
    骏马奔腾,芯向未来:SiC功率器件的“<b class='flag-5'>三个</b>必然”与丙午马年的产业跃迁

    altium designer 如何画短路两或者三个网络的封装?

    如何我画了一天线,有3引脚,TX1,TX2,GND, 但实际上这三个引脚是连在一起的。我导入pcb后,会报DRC警告,这个该怎么解决?
    发表于 02-12 19:30

    Python运行本地Web服务并实现远程访问

    本文介绍使用Python搭建本地Web服务并结合 ZeroNews 实现公网访问。
    的头像 发表于 02-06 11:39 435次阅读
    <b class='flag-5'>Python</b>运行本地Web服务并<b class='flag-5'>实现</b>远程访问

    三个经典开关电源实际问题解析

    下面三个关于多路输出电源连续工作模式的问题,测试一下您的电源设计知识。然后核对您的答案。
    的头像 发表于 01-22 16:08 4023次阅读
    <b class='flag-5'>三个</b>经典开关电源实际问题解析

    束管式光缆最简单三个步骤

    束管式光缆的安装或施工可简化为以下三个核心步骤,确保光纤传输性能稳定且操作高效: 第一步:路由规划与准备 路径勘察 确定光缆敷设路线,避开强电磁干扰源(如高压线)、尖锐物体或易受外力破坏的区域(如
    的头像 发表于 01-07 10:12 522次阅读

    京东关键词搜索商品列表的Python爬虫实战

    京东关键词搜索商品列表 Python 爬虫实战 你想要实现京东关键词搜索商品的爬虫,我会从 合规声明、环境准备、页面分析、代码实现、反爬优化 五方面展开,帮助你完成实战项目。 一、前
    的头像 发表于 01-04 10:16 1527次阅读

    Python调用API教程

    不同系统之间的信息交互。在这篇文章中,我们将详细介绍Python调用API的方法和技巧。 一、用Requests发送HTTP请求 使用Python调用API的第一步是发送HTTP
    的头像 发表于 11-03 09:15 1274次阅读

    未来工业AI发展的三个必然阶段

    与优化 能力的深层革命。 未来十年,工业AI的发展将经历三个清晰的阶段:  智能辅助 → 智能决策 → 自主优化 。这次进化,构成了工业从“人控机器”到“机器共智”的核心路径。 一、第一阶段:智能辅助(AI for Assistance) 时间窗口:
    的头像 发表于 10-27 15:47 655次阅读
    未来工业AI发展的<b class='flag-5'>三个</b>必然阶段

    termux如何搭建python游戏

    模拟,支持通过APT包管理安装软件。搭建Python游戏开发环境前需完成以下基础配置: 1. 更换国内源 为提升下载速度,需替换Termux默认源为清华源,执行以下命令: ```bash
    发表于 08-29 07:06

    怎么结合嵌入式,Linux,和FPGA三个方向达到一均衡发展?

    在嵌入式领域,不少人都怀揣着让嵌入式、Linux 和 FPGA 三个方向实现均衡发展的梦想,然而实践中却面临诸多挑战。就像备受瞩目的全栈工程师稚晖君,他从大学玩单片机起步,凭借将智能算法融入嵌入式而
    的头像 发表于 06-25 10:08 1082次阅读
    怎么结合嵌入式,Linux,和FPGA<b class='flag-5'>三个</b>方向达到一<b class='flag-5'>个</b>均衡发展?

    突破LuatOS开发瓶颈:三个二次开发必备知识揭秘!

    遇到LuatOS开发难题?或许你忽略了这三个关键常识。它们看似简单,却能破解资源管理、效率提升等瓶颈问题,为你的项目注入新活力,实现技术跃迁。 本期一起来了解LuatOS开发需要熟悉的几个常识
    的头像 发表于 06-23 15:05 552次阅读
    突破LuatOS开发瓶颈:<b class='flag-5'>三个</b>二次开发必备知识揭秘!