干货 | 一文搞定 pytest 自动化测试框架(二)
ccwgpt 2024-11-02 11:02 22 浏览 0 评论
本文节选自霍格沃玆测试学院内部教材,进阶学习。
在上一篇文章中分享了 pytest 的基本用法,本文进一步介绍 pytest 的其他实用特性和进阶技巧。
pytest fixtures
pytest 中可以使用 @pytest.fixture 装饰器来装饰一个方法,被装饰方法的方法名可以作为一个参数传入到测试方法中。可以使用这种方式来完成测试之前的初始化,也可以返回数据给测试函数。
将 fixture 作为函数参数
通常使用 setup 和 teardown 来进行资源的初始化。如果有这样一个场景,测试用例 1 需要依赖登录功能,测试用例 2 不需要登录功能,测试用例 3 需要登录功能。这种场景 setup,teardown 无法实现,可以使用 pytest fixture 功能,在方法前面加个 @pytest.fixture 装饰器,加了这个装饰器的方法可以以参数的形式传入到方法里面执行。
例如在登录的方法,加上 @pytest.fixture 这个装饰器后,将这个用例方法名以参数的形式传到方法里,这个方法就会先执行这个登录方法,再去执行自身的用例步骤,如果没有传入这个登录方法,就不执行登录操作,直接执行已有的步骤。
创建一个文件名为“test_fixture.py”,代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest
@pytest.fixture()
def login():
print("这是个登录方法")
return ('tom','123')
@pytest.fixture()
def operate():
print("登录后的操作")
def test_case1(login,operate):
print(login)
print("test_case1,需要登录")
def test_case2():
print("test_case2,不需要登录 ")
def test_case3(login):
print(login)
print("test_case3,需要登录")
在上面的代码中,测试用例 test_case1 和 test_case3 分别增加了 login 方法名作为参数,pytest 会发现并调用 @pytest.fixture 标记的 login 功能,运行测试结果如下:
plugins: html-2.0.1, rerunfailures-8.0, xdist-1.31.0, \
ordering-0.6, forked-1.1.3, allure-pytest-2.8.11, metadata-1.8.0
collecting ... collected 3 items
test_fixture.py::test_case1 这是个登录方法
登录后的操作
PASSED [ 33%]('tom', '123')
test_case1,需要登录
test_fixture.py::test_case2 PASSED \
[ 66%]test_case2,不需要登录
test_fixture.py::test_case3 这是个登录方法
PASSED [100%]('tom', '123')
test_case3,需要登录
============================== 3 passed in 0.02s ===============================
Process finished with exit code 0
从上面的结果可以看出,test_case1 和 test_case3 运行之前执行了 login 方法,test_case2 没有执行这个方法。
指定范围内共享
fixture 里面有一个参数 scope,通过 scope 可以控制 fixture 的作用范围,根据作用范围大小划分:session> module> class> function,具体作用范围如下:
- function 函数或者方法级别都会被调用
- class 类级别调用一次
- module 模块级别调用一次
- session 是多个文件调用一次(可以跨.py文件调用,每个.py文件就是module)
例如整个模块有多条测试用例,需要在全部用例执行之前打开浏览器,全部执行完之后去关闭浏览器,打开和关闭操作只执行一次,如果每次都重新执行打开操作,会非常占用系统资源。这种场景除了setup_module,teardown_module 可以实现,还可以通过设置模块级别的 fixture 装饰器(@pytest.fixture(scope="module"))来实现。
scope='module'
fixture 参数 scope='module',module 作用是整个模块都会生效。
创建文件名为 test_fixture_scope.py,代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest
# 作用域:module是在模块之前执行, 模块之后执行
@pytest.fixture(scope="module")
def open():
print("打开浏览器")
yield
print("执行teardown !")
print("最后关闭浏览器")
@pytest.mark.usefixtures("open")
def test_search1():
print("test_search1")
raise NameError
pass
def test_search2(open):
print("test_search2")
pass
def test_search3(open):
print("test_search3")
pass
代码解析:
@pytest.fixture() 如果不写参数,参数默认 scope='function'。当 scope='module' 时,在当前 .py 脚本里面所有的用例开始前只执行一次。scope 巧妙与 yield 组合使用,相当于 setup 和 teardown 方法。还可以使用 @pytest.mark.usefixtures 装饰器,传入前置函数名作为参数。
运行结果如下:
plugins: html-2.0.1, rerunfailures-8.0, \
xdist-1.31.0, ordering-0.6, forked-1.1.3,\
allure-pytest-2.8.11, metadata-1.8.0
collecting ... collected 3 items
test_fixture_yield.py::test_search1 打开浏览器
FAILED [ 33%]test_search1
test_fixture_yield.py:13 (test_search1)
open = None
def test_search1(open):
print("test_search1")
> raise NameError
E NameError
test_fixture_yield.py:16: NameError
test_fixture_yield.py::test_search2 PASSED \
[ 66%]test_search2
test_fixture_yield.py::test_search3 PASSED \
[100%]test_search3
执行teardown !
最后关闭浏览器
...
open = None
def test_search1(open):
print("test_search1")
> raise NameError
E NameError
test_fixture_yield.py:16: NameError
------ Captured stdout setup --------
打开浏览器
----- Captured stdout call -----
test_search1
===== 1 failed, 2 passed in 0.06s =====
Process finished with exit code 0
从上面运行结果可以看出,scope="module" 与 yield 结合,相当于 setup_module 和 teardown_module 方法。整个模块运行之前调用了 open()方法中 yield 前面的打印输出“打开浏览器”,整个运行之后调用了 yield 后面的打印语句“执行 teardown !”与“关闭浏览器”。yield 来唤醒 teardown 的执行,如果用例出现异常,不影响 yield 后面的 teardown 执行。可以使用 @pytest.mark.usefixtures 装饰器来进行方法的传入。
conftest.py 文件
fixture scope 为 session 级别是可以跨 .py 模块调用的,也就是当我们有多个 .py 文件的用例时,如果多个用例只需调用一次 fixture,可以将 scope='session',并且写到 conftest.py 文件里。写到 conftest.py 文件可以全局调用这里面的方法。使用的时候不需要导入 conftest.py 这个文件。使用 conftest.py 的规则:
- conftest.py 这个文件名是固定的,不可以更改。
- conftest.py 与运行用例在同一个包下,并且该包中有 init.py 文件
- 使用的时候不需要导入 conftest.py,pytest 会自动识别到这个文件
- 放到项目的根目录下可以全局调用,放到某个 package 下,就在这个 package 内有效。
案例
在运行整个项目下的所有的用例,只执行一次打开浏览器。执行完所有的用例之后再执行关闭浏览器,可以在这个项目下创建一个 conftest.py 文件,将打开浏览器操作的方法放在这个文件下,并添加一个装饰器 @pytest.fixture(scope="session"),就能够实现整个项目所有测试用例的浏览器复用,案例目录结构如下:
创建目录 test_scope,并在目录下创建三个文件 conftest.py,test_scope1.py 和 test_scope2.py。
conftest.py 文件定义了公共方法,pytest 会自动读取 conftest.py 定义的方法,代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest
@pytest.fixture(scope="session")
def open():
print("打开浏览器")
yield
print("执行teardown !")
print("最后关闭浏览器")
创建 test_scope1.py 文件,代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest
def test_search1(open):
print("test_search1")
pass
def test_search2(open):
print("test_search2")
pass
def test_search3(open):
print("test_search3")
pass
if __name__ == '__main__':
pytest.main()
创建文件“test_scope2.py”,代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class TestFunc():
def test_case1(self):
print("test_case1,需要登录")
def test_case2(self):
print("test_case2,不需要登录 ")
def test_case3(self):
print("test_case3,需要登录")
打开 cmd,进入目录 test_scope/,执行如下命令:
pytest -v -s
或者
pytest -v -s test_scope1.py test_scope2.py
执行结果如下:
省略...
collected 6 items
test_scope1.py::test_search1 打开浏览器
test_search1
PASSED
test_scope1.py::test_search2 test_search2
PASSED
test_scope1.py::test_search3 test_search3
PASSED
test_scope2.py::TestFunc::test_case1 test_case1,需要登录
PASSED
test_scope2.py::TestFunc::test_case2 test_case2,不需要登录
PASSED
test_scope2.py::TestFunc::test_case3 test_case3,需要登录
PASSED执行teardown !
最后关闭浏览器
省略后面打印结果...
执行过程中 pytest 会自动识别当前目录的 conftest.py,不需要导入直接引用里面的方法配置。应用到整个目录下的所有调用这里面的方法中执行。conftest.py 与运行的用例要在同一个 pakage 下,并且这个包下有 __init__.py 文件
自动执行 fixture
如果每条测试用例都需要添加 fixture 功能,则需要在每一要用例方法里面传入这个fixture的名字,这里就可以在装饰器里面添加一个参数 autouse='true',它会自动应用到所有的测试方法中,只是这里没有办法返回值给测试用例。
使用方法,在方法前面加上装饰器,如下:
@pytest.fixture(autouse="true")
def myfixture():
print("this is my fixture")
@pytest.fixture 里设置 autouse 参数值为 true(默认 false),每个测试函数都会自动调用这个前置函数。
创建文件名为“test_autouse.py”,代码如下:
# coding=utf-8
import pytest
@pytest.fixture(autouse="true")
def myfixture():
print("this is my fixture")
class TestAutoUse:
def test_one(self):
print("执行test_one")
assert 1 + 2 == 3
def test_two(self):
print("执行test_two")
assert 1 == 1
def test_three(self):
print("执行test_three")
assert 1 + 1 == 2
执行上面这个测试文件,结果如下:
...
test_a.py::TestAutoUse::test_one this is my fixture
执行test_one
PASSED
test_a.py::TestAutoUse::test_two this is my fixture
执行test_two
PASSED
test_a.py::TestAutoUse::test_three this is my fixture
执行test_three
PASSED
...
从上面的运行结果可以看出,在方法 myfixture() 上面添加了装饰器 @pytest.fixture(autouse="true"),测试用例无须传入这个 fixture 的名字,它会自动在每条用例之前执行这个 fixture。
fixture 传递参数
测试过程中需要大量的测试数据,如果每条测试数据都编写一条测试用例,用例数量将是非常宠大的。一般我们在测试过程中会将测试用到的数据以参数的形式传入到测试用例中,并为每条测试数据生成一个测试结果数据。
这时候可以使用 fixture 的参数化功能,在 fixture 方法加上装饰器 @pytest.fixture(params=[1,2,3]),就会传入三个数据 1、2、3,分别将这三个数据传入到用例当中。这里可以传入的数据是个列表。传入的数据需要使用一个固定的参数名 request 来接收。
创建文件名为“test_params.py”,代码如下:
import pytest
@pytest.fixture(params=[1, 2, 3])
def data(request):
return request.param
def test_not_2(data):
print(f"测试数据:{data}")
assert data < 5
运行结果如下:
...
test_params.py::test_not_2[1]PASSED [ 33%]测试数据:1
test_params.py::test_not_2[2] PASSED [ 66%]测试数据:2
test_params.py::test_not_2[3] PASSED [100%]测试数据:3
...
从运行结果可以看出,对于 params 里面的每个值,fixture 都会去调用执行一次,使用 request.param 来接受用例参数化的数据,并且为每一个测试数据生成一个测试结果。在测试工作中使用这种参数化的方式,会减少大量的代码量,并且便于阅读与维护。
多线程并行与分布式执行
假如项目中有测试用例 1000 条,一条测试用例需要执行 1 分钟,一个测试人员需要 1000 分钟才能完成一轮回归测试。通常我们会用人力成本换取时间成本,加几个人一起执行,时间就会缩短。如果 10 人一起执行只需要 100 分钟,这就是一种并行测试,分布式的场景。
pytest-xdist 是 pytest 分布式执行插件,可以多个 CPU 或主机执行,这款插件允许用户将测试并发执行(进程级并发),插件是动态决定测试用例执行顺序的,为了保证各个测试能在各个独立线程里正确的执行,应该保证测试用例的独立性(这也符合测试用例设计的最佳实践)。
安装
pip install pytest-xdist
多个 CPU 并行执行用例,需要在 pytest 后面添加 -n 参数,如果参数为 auto,会自动检测系统的 CPU 数目。如果参数为数字,则指定运行测试的处理器进程数。
pytest -n auto
pytest -n [num]
案例
某个项目有 200 条测试用例,每条测试用例之间没有关联关系,互不影响。这 200 条测试用例需要在 1 小时之内测试完成,可以加个-n参数,使用多 CPU 并行测试。运行方法:
pytest -n 4
进入到项目目录下,执行 pytest 可以将项目目录下所有测试用例识别出来并且运行,加上 -n 参数,可以指定 4 个 CPU 并发执行。大量的测试用例并发执行提速非常明显。
结合 pytest-html 生成测试报告
测试报告通常在项目中尤为重要,报告可以体现测试人员的工作量,开发人员可以从测试报告中了解缺陷的情况,因此测试报告在测试过程中的地位至关重要,测试报告为纠正软件存在的质量问题提供依据,为软件验收和交付打下基础。测试报告根据内容的侧重点,可以分为 “版本测试报告” 和 “总结测试报告”。执行完 pytest 测试用例,可以使用 pytest-HTML 插件生成 HTML 格式的测试报告。
安装
pip install pytest-html
执行方法
pytest --html=path/to/html/report.html
结合 pytest-xdist 使用
pytest -v -s -n 3 --html=report.html --self-contained-html
生成测试报告
如下图:
生成的测试报告最终是 HTML 格式,报告内容包括标题、运行时间、环境、汇总结果以及用例的通过个数、跳过个数、失败个数、错误个数,期望失败个数、不期望通过个数、重新运行个数、以及错误的详细展示信息。报告会生成在运行脚本的同一路径,需要指定路径添加--html=path/to/html/report.html 这个参数配置报告的路径。如果不添加 --self-contained-html 这个参数,生成报告的 CSS 文件是独立的,分享的时候容易千万数据丢失。
pytest 框架 assert 断言使用(附)
编写代码时,我们经常会做出一些假设,断言就是用于在代码中捕捉这些假设。断言表示为一些布尔表达式,测试人员通常会加一些断言来断定中间过程的正确性。断言支持显示最常见的子表达式的值,包括调用,属性,比较以及二元和一元运算符。Python使用 assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。
使用方法:
assert True #断言为真
assertnot False #断言为假
案例如下:
assert "h" in "hello" #判断h在hello中
assert 5>6 #判断5>6为真
assert not True #判断xx不为真
assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'} #判断两个字典相等
如果没有断言,没有办法判定用例中每一个测试步骤结果的正确性。在项目中适当的使用断言,来对代码的结构、属性、功能、安全性等场景检查与验证。
相关推荐
- 用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)