软件测试/测试开发丨利用 pytest 玩转数据驱动测试框架
ccwgpt 2024-11-02 11:03 26 浏览 0 评论
公众号搜索:TestingStudio 霍格沃兹测试开发的干货都很硬核
pytest架构是什么?
首先,来看一个 pytest 的例子:
def test_a():
print(123)
collected 1 item
test_a.py . [100%]
============ 1 passed in 0.02s =======================
输出结果很简单:收集到 1 个用例,并且这条测试用例执行通过。
此时思考两个问题:
- pytest 如何收集到用例的?
- pytest 如何把 python 代码,转换成 pytest 测试用例(又称 item) ?
pytest如何做到收集到用例的?
这个很简单,遍历执行目录,如果发现目录的模块中存在符合“ pytest 测试用例要求的 python 对象”,就将之转换为 pytest 测试用例。
比如编写以下 hook 函数:
def pytest_collect_file(path, parent):
print("hello", path)
hello C:\Users\yuruo\Desktop\tmp\tmp123\tmp\testcase__init__.py
hello C:\Users\yuruo\Desktop\tmp\tmp123\tmp\testcase\conftest.py
hello C:\Users\yuruo\Desktop\tmp\tmp123\tmp\testcase\test_a.py
会看到所有文件内容。
如何构造pytest的item?
pytest 像是包装盒,将 python 对象包裹起来,比如下图:
当写好 python 代码时:
def test_a:
print(123)
会被包裹成 Function :
<Function test_a>
可以从 hook 函数中查看细节:
def pytest_collection_modifyitems(session, config, items):
pass
于是,理解包裹过程就是解开迷题的关键。pytest 是如何包裹 python 对象的?
下面代码只有两行,看似简单,但暗藏玄机!
def test_a:
print(123)
把代码位置截个图,如下:
我们可以说,上述代码是处于“testcase包”下的 “test_a.py模块”的“test_a函数”, pytest 生成的测试用例也要有这些信息:
处于“testcase包”下的 “test_a.py模块”的“test_a测试用例:
把上述表达转换成下图:
pytest 使用 parent 属性表示上图层级关系,比如 Module 是 Function 的上级, Function 的 parent 属性如下:
<Function test_a>:
parent: <Module test_parse.py>
当然 Module 的 parent 就是 Package:
<Module test_parse.py>:
parent: <Package tests>
注意大小写:Module 是 pytest 的类,用于包裹 python 的 module 。Module 和 module 表示不同意义。
这里科普一下,python 的 package 和 module 都是真实存在的对象,你可以从 obj 属性中看到,比如 Module 的 obj 属性如下:
如果理解了 pytest 的包裹用途,非常好!我们进行下一步讨论:如何构造 pytest 的 item ?
以下面代码为例:
def test_a:
print(123)
构造 pytest 的 item ,需要:
- 构建 Package
- 构建 Module
- 构建 Function
以构建 Function 为例,需要调用其from_parent()方法进行构建,其过程如下图:
从函数名from_parent,就可以猜测出,“构建 Function”一定与其 parent 有不小联系!又因为 Function 的 parent 是 Module :
根据下面 Function 的部分代码(位于 python.py 文件):
class Function(PyobjMixin, nodes.Item):
# 用于创建测试用例
@classmethod
def from_parent(cls, parent, **kw):
"""The public constructor."""
return super().from_parent(parent=parent, **kw)
# 获取实例
def _getobj(self):
assert self.parent is not None
return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined]
# 运行测试用例
def runtest(self) -> None:
"""Execute the underlying test function."""
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
得出结论,可以利用 Module 构建 Function!其调用伪代码如下:
Function.from_parent(Module)
既然可以利用 Module 构建 Function, 那如何构建 Module ?
当然是利用 Package 构建 Module!
Module.from_parent(Package)
既然可以利用 Package 构建 Module 那如何构建 Package ?
别问了,快成套娃了,请看下图调用关系:
pytest 从 Config 开始,层层构建,直到 Function !Function 是 pytest 的最小执行单元。
如何手动构建item?
手动构建 item 就是模拟 pytest 构建 Function 的过程。也就是说,需要创建 Config ,然后利用 Config 创建 Session ,然后利用 Session 创建 Package ,…,最后创建 Function。
其实没这么复杂, pytest 会自动创建好 Config, Session和 Package ,这三者不用手动创建。
比如编写以下 hook 代码,打断点查看其 parent 参数:
def pytest_collect_file(path, parent):
pass
如果遍历的路径是某个包(可从path参数中查看具体路径),比如下图的包:
其 parent 参数就是 Package ,此时可以利用这个 Package 创建 Module :
编写如下代码即可构建 pytest 的 Module ,如果发现是 yaml 文件,就根据 yaml 文件内容动态创建 Module 和 module :
from _pytest.python import Module, Package
def pytest_collect_file(path, parent):
if path.ext == ".yaml":
pytest_module = Module.from_parent(parent, fspath=path)
# 返回自已定义的 python module
pytest_module._getobj = lambda : MyModule
return pytest_module
需要注意,上面代码利用猴子补丁改写了 _getobj 方法,为什么这么做?
Module 利用 _getobj 方法寻找并导入(import语句) path 包下的 module ,其源码如下:
# _pytest/python.py Module
class Module(nodes.File, PyCollector):
def _getobj(self):
return self._importtestmodule()
def _importtestmodule(self):
# We assume we are only called once per module.
importmode = self.config.getoption("--import-mode")
try:
# 关键代码:从路径导入 module
mod = import_path(self.fspath, mode=importmode)
except SyntaxError as e:
raise self.CollectError(
ExceptionInfo.from_current().getrepr(style="short")
) from e
# 省略部分代码...
但是,如果使用数据驱动,即用户创建的数据文件 test_parse.yaml ,它不是 .py 文件,不会被 python 识别成 module (只有 .py 文件才能被识别成 module)。
这时,就不能让 pytest 导入(import语句) test_parse.yaml ,需要动态改写 _getobj ,返回自定义的 module !
因此,可以借助 lambda 表达式返回自定义的 module :
lambda : MyModule
如何自定义module
这就涉及元编程技术:动态构建 python 的 module ,并向 module 中动态加入类或者函数:
import types
# 动态创建 module
module = types.ModuleType(name)
def function_template(*args, **kwargs):
print(123)
# 向 module 中加入函数
setattr(module, "test_abc", function_template)
综上,将自己定义的 module 放入 pytest 的 Module 中即可生成 item :
# conftest.py
import types
from _pytest.python import Module
def pytest_collect_file(path, parent):
if path.ext == ".yaml":
pytest_module = Module.from_parent(parent, fspath=path)
# 动态创建 module
module = types.ModuleType(path.purebasename)
def function_template(*args, **kwargs):
print(123)
# 向 module 中加入函数
setattr(module, "test_abc", function_template)
pytest_module._getobj = lambda: module
return pytest_module
创建一个 yaml 文件,使用 pytest 运行:
======= test session starts ====
platform win32 -- Python 3.8.1, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\yuruo\Desktop\tmp
plugins: allure-pytest-2.8.11, forked-1.3.0, rerunfailures-9.1.1, timeout-1.4.2, xdist-2.2.1
collected 1 item
test_a.yaml 123
.
======= 1 passed in 0.02s =====
PS C:\Users\yuruo\Desktop\tmp>
现在停下来,回顾一下,我们做了什么?
借用 pytest hook ,将 .yaml 文件转换成 python module。
作为一个数据驱动测试框架,我们没做什么?
没有解析 yaml 文件内容!上述生成的 module ,其内的函数如下:
def function_template(*args, **kwargs):
print(123)
只是简单打印 123 。数据驱动测试框架需要解析 yaml 内容,根据内容动态生成函数或类。比如下面 yaml 内容:
test_abc:
- print: 123
表达的含义是“定义函数 test_abc,该函数打印 123”。
注意:关键字含义应该由你决定,这里仅给一个 demo 演示!
可以利用 yaml.safe_load 加载 yaml 内容,并进行关键字解析,其中path.strpath代表 yaml 文件的地址:
import types
import yaml
from _pytest.python import Module
def pytest_collect_file(path, parent):
if path.ext == ".yaml":
pytest_module = Module.from_parent(parent, fspath=path)
# 动态创建 module
module = types.ModuleType(path.purebasename)
# 解析 yaml 内容
with open(path.strpath) as f:
yam_content = yaml.safe_load(f)
for function_name, steps in yam_content.items():
def function_template(*args, **kwargs):
"""
函数模块
"""
# 遍历多个测试步骤 [print: 123, print: 456]
for step_dic in steps:
# 解析一个测试步骤 print: 123
for step_key, step_value in step_dic.items():
if step_key == "print":
print(step_value)
# 向 module 中加入函数
setattr(module, function_name, function_template)
pytest_module._getobj = lambda: module
return pytest_module
上述测试用例运行结果如下:
=== test session starts ===
platform win32 -- Python 3.8.1, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\yuruo\Desktop\tmp
plugins: allure-pytest-2.8.11, forked-1.3.0, rerunfailures-9.1.1, timeout-1.4.2, xdist-2.2.1
collected 1 item
test_a.yaml 123
.
=== 1 passed in 0.02s ====
当然,也支持复杂一些的测试用例:
test_abc:
- print: 123
- print: 456
test_abd:
- print: 123
- print: 456
其结果如下:
== test session starts ==
platform win32 -- Python 3.8.1, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\yuruo\Desktop\tmp
plugins: allure-pytest-2.8.11, forked-1.3.0, rerunfailures-9.1.1, timeout-1.4.2, xdist-2.2.1
collected 2 items
test_a.yaml 123
456
.123
456
.
== 2 passed in 0.02s ==
利用pytest创建数据驱动测试框架就介绍到这里啦,希望能给大家带来一定的帮助。大家有什么不懂的地方或者有疑惑也可以留言讨论哈,让我们共同进步呦!
相关推荐
- 用Deepseek扩写土木工程毕业论文实操指南
-
用Deepseek扩写毕业论文实操指南一、前期准备整理现有论文初稿/提纲列清楚论文核心框架(背景、现状、意义、方法、数据、结论等)梳理好关键文献,明确核心技术路线二、Deepseek扩写核心思路...
- 985学霸亲授,DeepSeek也能绘6大科研图表,5分钟就出图
-
在实验数据处理中,高效可视化是每个科研人的必修课。传统绘图软件操作复杂、耗时费力,而智能工具DeepSeek的出现彻底改变了这一现状。本文将详解如何用DeepSeek一键生成六大科研常用图表,从思维导...
- AI写论文刷屏?大学生正在丢掉的思考力
-
一、宿舍深夜:当论文变成"Ctrl+C+V"凌晨两点的大学宿舍,小王对着电脑屏幕叹气。本该三天前开始写的近代史论文,此刻还一片空白。他熟练打开某AI写作网站,输入"论五四运动的...
- Grok在辅助论文写作上能不能既“聪明”又“可怕”?!
-
AcademicIdeas-学境思源AI初稿写作随着人工智能技术的飞速发展,论文写作这一学术任务正迎来新的助力。2025年2月18日,美国xAI公司推出了备受瞩目的Grok3模型,其创始人埃隆·...
- 大四论文沟通场景!音频转文字难题听脑AI来化解
-
大四学生都知道,写论文时和导师沟通修改意见,简直是“过关斩将”。电话、语音沟通完,想把导师说的修改方向、重点要求记下来,麻烦事儿可不少。手写记不全,用普通录音转文字工具,转完还得自己慢慢找重点,稍不注...
- 论文写作 | 技术路线图怎么画?(提供经典优秀模板参考)
-
技术路线图是一种图表或文字说明,用于描述研究目标、方法和实施计划。它展示了研究的整体框架和步骤,有助于读者理解研究的逻辑和进展。在课题及论文中,技术路线图是常见的一部分,甚至是一个类似心脏一样的中枢器...
- 25年信息系统项目管理师考试第2批论文题目写作建议思路框架
-
25年信息系统项目管理师考试第2批论文题目写作建议思路框架--马军老师
- 微信购物应尽快纳入法律框架(微信购物管辖)
-
符向军近日,甘肃省工商行政管理局发布《2016年上半年信息分析报告》。报告显示,微信网购纠纷迅猛增长,网络购物投诉呈上升趋势。投诉的主要问题有出售的商品质量不过关、消费者通过微信付款后对方不发货、购买...
- 泛珠三角区域网络媒体与腾讯微信签署《战略合作框架协议》
-
新海南客户端、南海网7月14日消息(记者任桐)7月14日上午,参加第四届泛珠三角区域合作网络媒体论坛的区域网络媒体负责人及嘉宾一行到腾讯微信总部座谈交流,并签署《战略合作框架协议》(以下简称《框架协...
- 离线使用、植入微信-看乐心Mambo手环如何打破框架
-
从2014年开始智能手环就成功进入人们的生活,至今已经演变出数据监测、信息推送、心率监测等诸多五花八门的功能,人们选择智能手环并不指望其能够改变身体健康情况,更多的是通过数据来正视自身运动情况和身体健...
- 华专网络:如何零基础制作一个网站出来?
-
#如何零基础制作一个网站出来?#你是不是觉得网站建设很复杂,觉得自己是小白,需求不明确、流程搞不懂、怕被外包公司坑……这些问题我都懂!今天华专网络就用大白话给你捋清楚建站的全流程,让你轻松get网站制...
- WAIC2024丨明日上午9点,不见不散!共同探讨智能社会与全球治理框架
-
大咖云集,硕果闪耀WAIC2024世界人工智能大会智能社会论坛将于7月5日9:00-12:00与你相约直播间WAIC2024上海杨浦同济大学哔哩哔哩多平台同步直播探讨智能社会与全球治理框架WAIC...
- 约基奇:森林狼换来戈贝尔时大家都在嘲笑 他们的阵容框架很不错
-
直播吧5月4日讯西部季后赛半决赛,掘金将迎战森林狼,约基奇赛前接受采访。约基奇说道:“当蒂姆-康纳利(森林狼总经理、前掘金总经理&曾选中约基奇)做了那笔交易(换来戈贝尔)时,每个人都在嘲笑他...
- 视频号带货为什么一个流量都没有?顶级分析框架送给你
-
视频号带货为什么一个流量都没有?遇到问题,一定是步步来分析内容,视频号带货一个流量都没有,用另外一个意思来讲,就可以说是零播放。为什么视频号带货一个流量都没有?跟你说再多,都不如来个分析框架。1、是否...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)