Pytest自动化学习笔记
UnitTest --- 单元测试框架
1、测试类必须继承自unittest.TestCase
2、测试方法必须以test_开头
3、类中方法的变量需要使用self.变量
4、用例执行顺序:按用例名ascii码先后顺序执行
用例执行顺序
# unittest框架默认根据ACSII码的顺序加载测试用例,数字与字母的顺序为: 0~9,A~Z,a~z。
对于类来说,class TestAxx 会优先于class TestBxx被执行。对于方法来说,test_aaa()方法会有优先于test_bbb()被执行。对于测试目录与测试文件来说,unittest同样是按照这个规则来加载测试用例的
方式一、通过测试用例的方法名称:根据ACSII码的顺序加载测试用例
方式二、通过addtest()添加用例的顺序
框架自带夹具的使用方法
# 模块级 --- 所有用例执行前/后只执行一次
setUpModule/tearDownModule
# 类级 --- 类中所有用例前/后只执行⼀次(类中)
setUpClass/tearDownClass
# 方法级 --- 每条用例前/后执行(类中类外都可以执行)
setUp/tearDown
代码示例
import unittest kk = 0 # 函数级开始 def setUpModule(): global kk; kk += 1 print(f"------->setup_module = {kk}") # 函数级结束 def tearDownModule(): global kk; kk += 1 print(f"------->teardown_module = {kk}") class Test_ABC(unittest.TestCase): # 函数级开始 def setUp(self): global kk; kk+=1 print(f"------->setup_method = {kk}") # 函数级结束 def tearDown(self): global kk; kk+=1 print(f"------->teardown_method = {kk}") def test_a(self): global kk; kk+=1 print(f"------->test_a = {kk}") # 类级开始 @classmethod # 这是类方法,必须加上这个装饰器 def setUpClass(cls): global kk; kk+=1 print(f"在每个类之前执行一次,如创建数据库连接 = {kk}") # 类级结束 @classmethod # 这是类方法,必须加上这个装饰器 def tearDownClass(cls): global kk; kk+=1 print(f"在每个类之后运行,如关闭数据库连接 = {kk}") if __name__ == '__main__': unittest.main() # 执行结果 ------->setup_module = 1 在每个类之前执行一次,如创建数据库连接 = 2 在每个类之后运行,如关闭数据库连接 = 6 ------->teardown_module = 7 ------->setup_method = 3 ------->test_a = 4 ------->teardown_method = 5 # 可以看到调用顺序为: 模块级(setUpModule、tearDownModule)=>类级(setUpClass、tearDownClass)=>方法级(setUp、tearDown)
参考网址
PyTest --- 单元测试框架
下载与安装
pip3 install pytest
pytest 与 unittest 对比
pytest 的特点
- 简单灵活,容易上手,文档丰富
- 支持参数化
- 能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)
- pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败case重复执行)、- - - pytest-xdist(多CPU分发)等
- 测试用例的skip和xfail处理
- 支持运行unittest编写的测试case
- 可以很好的和jenkins集成
- 支持 allure 报
# 测试用例的执行
pytest [路径/文件名::类名::方法名]
如果只输入 pytest,则会默认执行当前文件夹下所有以 test_开头(或_test 结尾)的文件
# 查看当前所有标志,包括内置标记和自定义标记
pytest --markers
# 查看 pytsest 配置选项
cmd下执行 pytest -h 或者 pytest --help
PyTest 的运行流程
1. pytest插件和conftest.py文件配置初始化等,创建测试session会话
2. 创建session完以后,执行collection收集测试用例之前的阶段。会在terminal终端打印一些环境信息,比如插件版本,python版本,操作平台这些等
3. 测试用例收集以及生成测试输入的过程,这里还可能包括根据关键字keywords和标签名marker筛选测试用例的过程
- 生成基本测试项
- 根据 @pytest.mark.parametrize 参数化生成对应测试项
- 所有测试项收集完毕以后调用,一般用来进行测试用例的重新排序和二次过滤; 通过 :: 语法筛选测试用例
4. 执行筛选过的测试用例
- setup (如果有)
- 执行fixture函数的setup过程(是否执行取决于fixture是否需要创建)
- 执行实际的测试过程代码
- 执行fixture函数的teardown过程(如果有)。
- teardown 和 log 打印的过程 (如果有)
- 返回测试用例的各个测试阶段的result
5. 所有测试执行完毕之后,返回 exit status 之前的阶段。会向terminal终端打印一些summary信息,比如测试用例的pass,fail,error数量之类的总结信息
6. session结束以后,整个process退出之前的阶段, 释放内存
pytest exit code 含义清单
Exit code 0:所有用例执行完毕,全部通过
Exit code 1:所有用例执行完毕,存在Failed的测试用例
Exit code 2:用户中断了测试的执行
Exit code 3:测试执行过程发生了内部错误
Exit code 4:pytest 命令行使用错误
Exit code 5:未采集到可用测试用例文件
pytest 命令参数及含义
参数 | 含义 |
---|---|
-v|--verbose | 更加详细的输出; 测试的名字和结果都会显示出来,而不仅仅是一个点或字符。用例执行成功或者失败会在每行【即每一个测试用例】后显示PASSED/FAILED |
-q|--quiet | 与-v(--verbose)的作用相反,-q的作用是简化输出信息;不显示每个用例的详细信息; 可以与 --tb=line(进打印异常代码的位置)结合使用 |
-s | 允许终端打印测试输出,例如 print 输出 |
--co|--collect-only | 展示在给定的配置下哪些测试用例会被运行; 只收集展示但不会执行用例 |
-k | 用于明确指定希望运行的测试用例, 可正则匹配方法名和文件名, 可使用表达式关键字 or 和 not |
-m=smoke | 指定标签名,即标识名,参数不可带单引号,只能是双引号,也是使用表达式关键字 or 和 and 和 not |
--lf|--last-failed | 定位失败的测试用例并且重新运行 |
--ff|--failed-first | 首先运行上一次失败的测试用例,然后才会运行剩下被选中的所有的测试用例 |
--nf|--new-first | 首先运行最新的测试文件下的测试用例,然后再按照文件最后修改时间排序运行其余文件下的测试用例 |
-n=2 | 多线程数 |
--reruns=2 | 失败用例重复跑的次数, |
-x | 出现 1 个用例报错即测试停止, 保留失败用例的执行环境,用与Debug |
--maxfail=2 | 用例报错最大几个即测试停止 |
-k="web_" | 根据字符用例名过滤 ;可过滤文件名,类名,方法名/函数名 |
--html | 插件: pytest-html ; 生成 HTML 测试报告 |
--result-log=path | 将执行结果保存到指定路径下 |
--durations=num | 显示执行最慢的测试用例,一般用于调优,注意如果要显示执行时间为 0 秒的用例时,要加上参数-vv |
-l | 用例运行失败时,打印用例中的局部变量 |
--count=num | 标识所有测试项执行次数 |
--junit-xml=path | 输出xml文件格式,在与jenkins做集成时使用 |
--cache-clear | 设置每次运行前清除之前的缓存 |
--cache-show="..*" | 模糊匹配缓存,显示缓存 |
--sw|--stepwise | 在测试失败时退出,下次从上次失败的测试继续 |
--runxfail | 报告xfail测试的结果,如果它们未被标记 |
--disable-warnings | 忽略 pytest 警告 |
--noconftest | 不要加载任何conftest.py文件 |
参数的使用的指南
测试用例关键字匹配 = -k
# 该参数的赋值可使用表达式构造;匹配不区分大小写;例如:
-k "user or banner" # 表示文件名或方法名中包含 user 或 banner 字符串的
-k "not user" # 表示不包含 user 字符串的
-k "TestClass and not pig" # 表示执行 TestClass 类中不存在 pig 字符的方法
测试用例 mark 匹配 = -m
# 该参数的赋值可使用表达式构造;匹配不区分大小写;例如:
-m "beryl and suosuo" # 表示运行同时标记有 suosuo 和 beryl 标识的
-m "erek or banner" # 表示运行标记为 erek 或 banner 标识的
-m "slow and not faster" # 表示运行有slow和没有faster标识的用例
# 注意:
自定义 mark 标识要求必须在配置中注册,当使用 @pytest.mark.XXX 自定义标记时,当在执行测试用例追加命令行参数 -m= 标记名称时,虽然不会影响测试执行,但是在执行后会出现告警提示
测试用例忽略匹配 = --ignore
忽略某一个目录下的所有用例或是某一类特征类脚本用例,除了可以在配置文件中指定 norecursedirs 参数,在命令行中也存在支持的参数
# 忽略 c 文件夹下的脚本
--ignore=all/c # c后面不要有/,也不要写成c/*
# 注意,若想忽略多个目录,可加多个 --ignore 参数即可,中间用空格分隔。
# 忽略某一类目录下的脚本
pytest --ignore-glob='*_a_test' # glob 模糊匹配文件夹,这会忽略目录名以 '_a_test' 为后缀目录下的所有脚本
全局配置文件指南 == pytest.ini
pytest.ini文件是pytest的主配置文件;可以改变pytest的运行方式;它是一个固定的文件pytest.ini文件,读取配置信息,按指定的方式去运行。
pytest.ini文件的位置一般放在项目的根目录下,不能随便放,也不能更改名字
[pytest]
# 配置默认的命令行选项; 这个参数在我们需要在命令行中输入大一堆指令来执行测试用例时会用到,这个时候就可以配置文件中配置这个参数来代替,省掉很多重复的工作
addopts = -vs -n=2 --html ./report.html -m smoke
# 限定测试用例的搜索范围, 该值是一系列相对于根目录的路径, 可以配置多个,多个用空格隔开;
# 只有在pytest未指定【文件目录】参数或测试用例标识符时,该选项才会启用
testpaths = ./
# 配置搜索匹配的测试用例的【文件】正则表达式名称, 可以配置多个,多个用空格隔开
python_files = test_*.py
# 配置搜索匹配的测试用例的【类】正则表达式名称, 可以配置多个,多个用空格隔开
python_classes = Test*
# 配置搜索匹配的测试用例的【方法】正则表达式名称, 可以配置多个,多个用空格隔开
python_functions = test
markers = # 注册自定义标签
smoke: xx # key 表示标签名,value 表示标签标签符
name: sf
# 配置控制台是否实时输出日志, 默认为 False
# 可配置为: log_cli=True 或 log_cli=False, 或者 log_cli=1 或 log_cli=0 ; 若为 False , 则每个测试项只会显示一行且尾随整个项目的执行进度
log_cli = False
# 表示 pytest 在递归迭代收集测试用例的时所要忽略的目录;
# pytest在收集测试用例的时候,会递归遍历当前目录下的所有子目录,当我们需要某些目录下的用例不要执行时,就可以通过设置norecursedirs参数来实现这个功能
norecursedirs = venv report util log # 多个目录需要空格分开,可以配置多个
# 指定pytest的最低版本; 如果使用版本(小于3.0)pytest运行测试,就会得到一个报错信息
minversion = 3.0
# 设置xfail_strict=true可以让那边标记为@pytest.mark.xfail 但实际通过显示为XPASS 的测试用例被报告为失败
xfail_strict
# 注意:
1. 当 pytest.ini 配置文件的参数与执行测试用例文件的命令行参数重复时,命令行参数值会覆盖ini配置文件中定义的参数值
2.
测试标记【装饰器】配置
调整用例的执行顺序
# 安装
pip install pytest-ordering
# Config
@pytest.mark.last # 最后执行
@pytest.mark.run(order=1) # 设定次序
设定用例执行次数
# install
pip install pytest-repeat
# config
@pytest.mark.repeat(n) # 执行 n 次, 然后向下执行
跳过用例执行
# skip和skipif允许你跳过不希望运行的测试; 也可特定的条件,不执行标识的测试函数
skipif(condition, reason=None)
# 常用参数:
condition:跳过的条件
reason:标注原因
@pytest.mark.skip() # 无条件跳过 mark
@pytest.mark.skipif(condition, reason="xxx") # condition 条件为真时跳过 mark
# 若需在代码执行过程中判定 跳过,可执行如下代码:
pytest.skip("xxxx")
skip 文件或目录
# 1. 如果缺少某些导入,则跳过模块中的所有测试
pexpect = pytest.importorskip("pexpect")
# 2. 无条件地跳过模块中的所有测试:
pytestmark = pytest.mark.skip("all skip")
# 3. 根据某些条件跳过模块中的所有测试
pytestmark = pytest.mark.skipif(sys.platform == "win32", "tests for linux → only"
标记预期失败用例
# xfail 标记预计失败的用例。 一个常见的例子则是已知功能尚未实施,或存在尚未修复的Bug。当测试通过时尽管预计会失败(标记 pytest.mark.xfail ),它是一个xpass,将在测试摘要中报告; 即若是执行成功了反而是失败了
xfail(condition=None, reason=None, raises=None, run=True, strict=False)
# 常用参数
condition:预期失败的条件
reason:失败的原因
若condition 条件满足的时候, 则标记用例预期失败,
若condition 条件不满足则正常执行
给予用例参数预配置
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
# 常用参数
argnames:多个参数名以逗号链接的一个字符串,
argvalues:参数对应值,类型必须为 list;存在几组参数则该用例执行几次
冒烟用例的设置 --- 自定义标识
# 标签可自定义
@pytest.mark.smoke # smoke 为自定义标签名
在命令中以 '-m' 参数指定, 当存在多个标签时,则需要关系关键字链接:例如
"smoke or teacher" # 执行这两个标签的用例
在配置文件中的配置
# 如若未在配置文件下配置时,就直接使用自定义标识名, 结果将告警;
markers =
smoke: xxx
tercher: ssf
配置文件 -- conftest.py
# conftest.py是什么?--- 类似于工具类或者是公约数代码的存放地
conftest.py是fixture函数的一个集合,可以理解为公共的提取出来放在一个文件里,然后供其它模块调用。不同于普通被调用的模块,conftest.py使用时不需要导入,Pytest会自动查找
# 使用场景
如果我们有很多个前置函数,写在各个py文件中是不很乱?再或者说,我们很多个py文件想要使用同一个前置函数该怎么办?这也就是conftest.py的作用
# 使用原则
1. conftest.py这个文件名是固定的,不可以更改。
2. conftest.py与运行用例在同一个包下,并且该包中有__init__.py文件使用的时候
3. 在编写不需要导入conftest.py,会自动寻找。
# 该文件特点
1、可以跨.py文件调用,有多个.py文件调用时,可让conftest.py只调用了一次fixture,或调用多次fixture
2、conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
3、不需要import导入 conftest.py,pytest用例会自动识别该文件,放到项目的根目录下就可以全局目录调用了,如果放到某个package下,那就在该package内有效,可有多个conftest.py
4、conftest.py配置脚本名称是固定的,不能改名称
5、conftest.py文件不能被其他文件导入
6、所有同目录测试文件运行前都会执行conftest.py文件
# 应用场景
1、每个接口需共用到的token
2、每个接口需共用到的测试用例数据
3、每个接口需共用到的配置信息
测试夹具指南
# 作用
1.做测试前的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现
2.做测试后的环境清理,比如:网口是否 UP 及参数重置, IP 是否 flush, channel 是否关闭, 虚拟端口和垃圾文件是否删除,
3.测试用例的前置条件可以使用fixture实现
4.支持经典的xunit fixture ,像unittest使用的setup和teardown
5.fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,
综上:fixture却可以解决这些个问题
框架自带初级夹具
# 目的: 用于存置测试前期准备和后期扫尾的代码工作;
# 作用于当前模块中所有方法的前后置
函数级 #(setup_function/teardown_function) --- 表示类外部的函数
方法级 #(setup_method/teardown_method) --- 表示类中的函数
类级 # (setup_class/teardown_class)
模块级 #(setup_module/teardown_module)
# 注意:
1. 全都都为小写 , 和 UnitTest 不一样
2. 没有会话级别的,即,不存在 setup_session 和 teardown_session
# 内置获取自定义参数值的夹具
request.config.getoption('cmdopt')
Fixture 装饰器夹具
# 目的
实现全局的应用的前置和后置,
# 注意:
1. 可以给类和方法添加夹具
2. conftest.py 配置脚本名称是固定的,不能改名称
3. conftest.py 使用时不需要导入,Pytest会自动查找
4. 夹具优先于测试用例代码执行,且优先于ku
5. 一定要注意夹具的可执行次数和执行时机,例如: session 级别的夹具,甭管你注册几次,它只会执行一次
# fixture 相比内置夹具 setup 和 teardown 的优势
1. 命名方式灵活,不局限于setup和teardown这几个命名
2. conftest 可以配置数据共享,
3. scope支持四个级别参数 "function" (默认), "class", "module" or "session"
4. 可通过 yield语句为测试函数区分前置条件和后置清理
# 全局的配置文件, 否则只能在定义所处模块中使用,类似于pytest全局配置文件,无需导入,该框架所规定的;
# 可存在任意的 Pkg 中,Pkg 要有__init__.py, 在项目中该文件可以有多个, 只需注意作用域的范围即可;
conftest.py
# 作用域大小
session > package > module > class > function
# 注意:
1. function级别 不区分类中的 method 和 类外的 function
# 定义装饰器函数
@pytest.fixture()
# 参数讲解
参数讲解
1. scope="" # 定义作用域范围,默认为 fuction; 其他可配置参数: class , module , session 2. autouse=Bool # 表示在作用域范围内所覆盖到的用例是否自动执行该夹具, 值为一个布尔值;默认为 False; 3. params: -> iterable object # 定义夹具为被应用的用例所传达的参数列表值 4. ids: # 当为用例传参时,为所传的每一个参数所设置的一个变量名,意义不大; 4. name: -> str # 定义夹具名称别名;在用例中使用该夹具时,该夹具原本的名称即失效了,只能使用这个别名;
Fixture 的作用范围
function # 默认作用域;每一个函数或方法都会调用,默认语法为: @pytest.fixture(scope='function')或 @pytest.fixture()
class: # 每一个类调用一次,一个类中可以有多个方法
module: # 每一个.py文件调用一次,一个模块中可存在多个function和class
package # 每个文件夹调用一次
session:# 多个文件调用一次,可以跨.py文件调用, 项目测试开始前执行一次
Fixture 通过 yield 实现teardown后置操作
实例 001
import pytest
# 此时,login函数是一个测试固件,相当于实现了setup_xxx&teardown_xxx的功能。
@pytest.fixture()
def login():
############# 以下的代码相当于setup部分 ###########
print('登录系统')
token = 'a1b23c'
yield token
############# 以下的代码相当于teardown部分 ###########
print('退出登录')
# 在测试函数里, 通过形参声明要使用的测试固件
def test1(login):
# login参数的值是测试固件函数的返回值
print('执行测试 test1: ', login)
print('测试1')
def test2(login):
print('执行测试 test2: ', login)
print('测试2')
# 通过python解释器执行需要以下代码
if __name__ == '__main__':
pytest.main(["-s", "test_yieldDemo.py"])
执行结果如下:
# 注意:return和yield两个关键字都可以返回值;
yield关键字返回值后,后面的代码还会继续运行;【由于实例1中fixture函数login需要返回token,而且还需要继续执行teardown后置操作:所以选择yield关键字所以后面代码还会继续运行】
return关键字返回值后,后面的代码不会继续运行;
实例 002 --- module 级别的后置
import pytest
@pytest.fixture(scope="module")
def open():
print("打开浏览器,并且打开百度首页")
yield
print("执行teardown!")
print("最后关闭浏览器")
def test_s1(open):
print("用例1:搜索python-1")
def test_s2(open):
print("用例2:搜索python-2")
def test_s3(open):
print("用例3:搜索python-3")
if __name__ == "__main__":
pytest.main(["-s", "test_fixturemodule2.py"])
执行结果:
实例 003 --- session 会话级别的后置
Fixture 通过 yield 实现 session 会话级别的后置 -- 代码示例
import pytest
@pytest.fixture(scope="session")
def open():
# 会话前置操作setup
print("===打开浏览器open===")
yield
# 会话后置操作teardown
print("===关闭浏览器open===")
@pytest.fixture
def login(open):
# 方法级别前置操作setup
print("===登陆操作login===")
name = "===账号==="
pwd = "===密码==="
# 返回变量
yield name, pwd
# 方法级别后置操作teardown
print("===登录成功login===")
def test_case1(login):
print("===测试用例1===")
# 返回的是一个元组
print(login)
# 分别赋值给不同变量
name, pwd = login
print(name, pwd)
assert "账号" in name
assert "密码" in pwd
def test_case2(login):
print("===测试用例2===")
print(login)
执行结果:
注意
如果其中一个用例在执行时出现异常,不影响yield后面的teardown执行,各个用例运行之间结果互不影响,并且全部用例执行完之后,yield唤起teardown操作
但是fixture函数如果在setup执行期间发生异常,那么pytest是不会去执行yield后面的teardown内容
yield 用例在执行时出现异常 -- 代码示例
import pytest
@pytest.fixture(scope="module")
def open():
print("打开浏览器,并且打开百度首页")
yield
print("执行teardown!")
print("最后关闭浏览器")
def test_s1(open):
print("用例1:搜索python-1")
# 如果第一个用例异常了,不影响其他的用例执行
raise NameError # 模拟异常
def test_s2(open):
print("用例2:搜索python-2")
def test_s3(open):
print("用例3:搜索python-3")
if __name__ == "__main__":
pytest.main(["-s", "tear_yield.py"])
执行结果:
yield关键字+with上下文管理器的结合使用
yield 关键字 也可以配合 with 上下文管理器 语句使用。【使得代码更加精简】
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection
request.addfinalizer()将定义的函数注册为终结函数
除了yield可以实现teardown,我们也可以通过 request.addfinalizer() 的方式去注册终结函数来实现 teardown 用例的后置操作
addfinalizer 的用法跟 yield 是不同的, addfinalizer 需要你去注册一个或多个作为终结器使用的函数
request.addfinalizer 注册单个终结函数 -- 代码示例
import pytest
@pytest.fixture(scope="module")
def test_addfinalizer(request):
# 前置操作setup
print("===打开浏览器===")
test = "test_addfinalizer"
def fin():
# 后置操作teardown
print("===关闭浏览器===")
request.addfinalizer(fin)
# 返回前置操作的变量
return test
def test_case(test_addfinalizer):
print("===最新用例===", test_addfinalizer)
执行结果:
初此之外,addfinalizer 函数 比 yield 还有一个优点是,addfinalizer 可以注册多个终结函数。当注册多个终结函数时,用例的后置操作同时会执行完所有的终结函数;
【注意】终结函数(用例后置操作函数)的执行顺序与其在fixture函数中注册的顺序相反(即先注册的终结函数后执行,后注册的终结函数先执行)
request.addfinalizer 注册多个后置函数 -- 代码示例
import pytest
@pytest.fixture()
def demo_addfinalizer(request):
print("====setup====")
def fin1():
print("====teardown1====")
def fin2():
print("====teardown2====")
def fin3():
print("====teardown3====")
# 注册fin1、fin2、fin3为终结函数
request.addfinalizer(fin1)
request.addfinalizer(fin2)
request.addfinalizer(fin3)
def test_case1(demo_addfinalizer):
print("====执行用例test_case1====")
def test_case2(demo_addfinalizer):
print("====执行用例test_case2====")
def test_case3(demo_addfinalizer):
print("====执行用例test_case3====")
if __name__ == '__main__':
pytest.main([__file__, '-s'])
执行结果:
1. 当执行测试用例时setup前置操作函数的代码执行错误或者发生异常时,addfinalizer 注册的终结函数依旧会执行。
2. yield 关键字可以返回setup前置操作函数中生成的测试数据,且 yield 关键字返回测试数据之后后续的代码依然可以运行。且后续执行的代码充当teardown后置操作函数。
3. addfinalizer 函数可以将一个或者多个函数注册为终结函数(一个或多个函数必须在fixture函数中定义),此时的终结函数为teardown后置操作函数;且最后可以使用 return 关键字返回setup前置操作函数生成的测试数据
向用例传参
params_li = ["11","22","33", [1, 2, 3]] # 可是: list, tuple , dict @pytest.fixture(scope = "class", params = params_li, autouse = True) def mft_browser(request): # request 为规定名称 print("前置") yield request.param # param 也为规定名称,表示params参数当中的每一个元素 print("后置") # 夹具要实现多次运行被作用用例且每次传参的话,则就要求该夹具得是一个可迭代对象, 故此种情况夹具中就不能使用 return 关键字; # 若夹具当中使用了 return 关键字而不存在 yield 的话,则无法实现后置应用;
代码示例
001
import pytest class Test_ABC(): @pytest.fixture(params=["suosuo", "duoduo"]) def myfix(self, request): print("before fixture") yield request.param print("after fixture") def test_a(self, myfix): print(f"------->test_a = {myfix}") assert 1 if __name__ == '__main__': pytest.main(["-vs", "./dd02.py"]) # 执行结果 dd02.py::Test_ABC::test_a[suosuo] before fixture ------->test_a = suosuo after fixture PASSED dd02.py::Test_ABC::test_a[duoduo] before fixture ------->test_a = duoduo after fixture PASSED
002
为类添加夹具
# 使用标记的方法,参数为字符串 夹具的名称
@pytest.mark.usefixtures("before")
代码示例
001
import pytest @pytest.fixture(scope="class") def myfix(): print("before fixture") yield print("after fixture") @pytest.mark.usefixtures("myfix") class Test_ABC(): def test_a(self): print("------->test_a") assert 1 if __name__ == '__main__': pytest.main(["-vs", "./dd02.py"]) # 执行结果 dd02.py::Test_ABC::test_a before fixture ------->test_a after fixture PASSED
002
import pytest @pytest.fixture() # fixture标记的函数可以应用于测试类外部 def before(): print("before fixture") yield print("after fixture") @pytest.mark.usefixtures("before") class Test_ABC: def setup(self): print("------->setup") def test_a(self, before): # 当类添加了夹具后, print("------->test_a") assert 1 if __name__ == '__main__': pytest.main(["-s", "dd022.py"]) # 执行结果 dd022.py::Test_ABC::test_a before fixture ------->test_a after fixture PASSED
003
import pytest @pytest.fixture() # fixture标记的函数可以应用于测试类外部 def before(): print("------->before") @pytest.mark.usefixtures("before") class Test_ABC: def setup(self): print("------->setup") def test_a(self): print("------->test_a") assert 1 if __name__ == '__main__': pytest.main(["-sv", "test_abc.py"]) # 执行结果: test_abc.py ------->before # 发现before会优先于测试类运行 ------->setup ------->test_a
夹具的嵌套
当测试用例作用于多个夹具时,所呈现效果和通常一个方法被多个装饰器修饰时是一样的,类似于俄罗斯套娃一样
因为夹具本质上就是一个装饰器,所以道理是相通的;
# 注意: 要特别注意不同的修饰器之间的嵌套顺序即可;
代码示例
测试用例参数化 -- 数据驱动
@pytest.mark. parametrize 装饰器可以实现对测试用例的参数化,方便测试数据的获取。
# 方法:
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
单个参数【测试方法入参只有一个参数】
import pytest
class Test_D:
@pytest.mark.parametrize("a", [1, 2, 3]) # 参数a被赋予3个值,test_a将会运行3遍
def test_a(self, a): # 参数必须和parametrize里面的参数一致
print('\n------------------> test_a has ran, and a = {}'.format(a))
assert 1 == a
if __name__ == '__main__':
pytest.main(['-s', 'xxxxxx.py'])
执行结果:
多个参数【测试方法入参有多个参数】
import pytest
class Test_D:
@pytest.mark.parametrize("a,b", [(1, 2), (2, 3), (3, 4)]) # 参数a,b均被赋予三个值,函数会运行三遍
def test_b(self, a, b): # 参数必须和parametrize里面的参数一致
print('\n------------------> test_b has ran, and a = {}, b = {}'.format(a, b))
assert 1
if __name__ == '__main__':
pytest.main(['-s', 'xxxxx.py'])
执行结果:
断言指南
allert 1 == 2
allert 33 in [11, 33, 55]
allert 33 <= 44
allert False
allert len(list(range(1, 10))) == 3
插件的安装与配置
构造测试报告 --- HTML
# 第一种: HTML简单页面
# install
pip3 install pytest-html
# 执行参数为:
--html # 定义 html 文件名
# 第二种: HTML图表格式
# install
pip3 install allure-pytest
# 执行参数为:
--alluredir <Dir> # 定义日志保存目录
--clean-alluredir # 清除旧有的 Allure 日志
# 注意: pytest 使用该参数只是造就了可通过 Allure 生成测试报告的前期 JSon 格式的报告数据;
图表格式 --- Allure
Allure Report Documentation --- 官方文档
# 1. Mac 下安装
brew install allure
# 验证
allure --version
# 2. 通过添加 Pytest 执行测试参数生成 Json 格式的临时报告
--alluredir ./temp
# 3. 通过 allure 工具生成 Allure 报告 --- 通过 Pytest 的 Json 格式的报告
allure generate `<JSON格式报告所在目录>` -o ./report --clean # --clean 可简写为 -c
# 不指定生成报告路径, 默认情况下会当前路径下生成allure-report的报告目录,里面的 HTML 文件双击选择浏览器即可查看;
# 转换为 python 代码执行
os.system('allure generate ./temp -o ./report --clean')
# 打开 Allure报告
allure open `<JSON格式报告所在目录>` # 也可手动打开
# 第三步骤是直接根据 JSON 格式报告生成了 Allure 格式HTML 报告, 即持久化保存在了指定的目录;
# 除了该方式,您若只是简单想在线浏览一下报告数据,并不想在本地生成 Allure 报告数据,则还有第二种方式查看报告;
# 它的原理是Allure在本地开了一个Web服务,客户机则可通过IP地址和端口号远程查看allure报告,当服务退出时则自动关闭并销毁;
# 命令如下:
allure serve `<JSON格式报告所在目录>`
失败重试
# install
pip install -U pytest-rerunfailures
# 【注意】
重复运行失败的测试用例时,对应的fixture或者setup函数也会重新执行(例如:scope参数为method的fixture前置函数)
# 参数
--reruns=NUM # 针对所有测试项, # NUM表示重试的次数
--reruns-delay # 指定下次测试重新开始之前等待的秒数
# 装饰器用法
@pytest.mark.flaky(reruns = 2, reruns_delay = 2) # 针对单个测试项,
# 【注意】
如果使用装饰器的方式指定了测试用例的重新运行次数,则在命令行参数中添加–reruns对这些测试用例是不会生效的
# 兼容性问题:
不可以和fixture装饰器一起使用: @pytest.fixture()
该插件与pytest-xdist的 --looponfail 标志不兼容
该插件与核心–pdb标志不兼容
多线程或分布式运行测试
平常我们功能测试用例非常多时,比如有1千条用例,假设每个用例执行需要1分钟,如果单个测试人员执行需要1000分钟才能跑完
当项目非常紧急时,会需要协调多个测试资源来把任务分成两部分,于是执行时间缩短一半,如果有10个小伙伴,那么执行时间就会变成十分之一,大大节省了测试时间
为了节省项目测试时间,10个测试同时并行测试,这就是一种分布式场景
分布式执行用例的原则
用例之间是独立的,没有依赖关系,完全可以独立运行
用例执行没有顺序要求,随机顺序都能正常执行
每个用例都能重复运行,运行结果不会影响其他用例
# 注意:要根据自己电脑的 cpu 数指定,电脑不好肯定会直接让电脑卡死
# install
pip3 install pytest-xdist
# 执行参数为 -n 2
多重校验
# install
pip3 install pytest-assume
# 命令
pytest.assume( 2 == 2)
用例依赖
使用该插件可以标记一个testcase作为其他testcase的依赖,当依赖项执行失败时,那些依赖它的test将会被跳过。
用 @pytest.mark.dependency()对所依赖的方法进行标记,使用@pytest.mark.dependency(depends=["test_name"])引用依赖,test_name可以是多个
# install
pip install pytest-dependency
# mark
@pytest.mark.dependency(depends = ["xxx001", "xxx02"])
执行时间超时设置
# install
pip3 install pytest-timeout
# 装饰器使用方法
@pytest.mark.timeout(xx)
用例重复执行配置
# install
pip3 install pytest-repeat
# 装饰器使用方法 --- 指定单个用例重复执行次数
@pytest.mark.repeat(xx)
# 参数
--count=2 # 指定所有用例重复执行次数,直接就是运行次数
# 注意:
通过设置 pytest -x 和 pytest-repeat 命令行参数的结合使用就能实现重复运行测试用例过程中直到遇到第一次失败用例就停止运行
调整用例的执行顺序
1.标记于被测试函数,@pytest.mark.run(order=x)
2.根据order传入的参数来解决运行顺序
3.order值全为正数或全为负数时,运行顺序:值越小,优先级越高
4.正数和负数同时存在:正数优先级高
# 注:默认情况下,pytest是根据测试方法名由小到大执行的,可以通过第三方插件包改变其运行顺序。
# 安装
pip install pytest-ordering
# Config
@pytest.mark.last # 最后执行
@pytest.mark.run(order=1) # 设定次序
执行多个断言
它允许在一个测试函数中执行多个断言,即使其中一个断言失败,其他断言仍然会被执行。这对于需要执行多个断言的测试场景非常有用,因为它可以在一个测试运行中提供所有失败的断言信息,而不是在第一个失败的断言处停止
# 安装
pip install pytest-check
# 命令实例
from pytest_check import check
def test_example():
with check:
check.equal(1, 2)
check.is_none(None)
check.is_true(False)
萌萌哒表情包
# install
pip install pytest-emoji
【可将表情符号添加到测试结果报告中】
# 参数
--emoji
用例执行进度条
在我们进行自动化测试的时候,用例往往是成百上千,执行的时间是几十分钟或者是小时级别。有时,我们在调试那么多用例的时候,不知道执行到什么程度了,而pytest-sugar插件能很好解决我们的痛点;
# install
pip3 install pytest-sugar
实时输出错误信息
1. test 运行全部用例的时候,在控制台会先显示用例的运行结果(.或F);等待用例全部运行完成后最后把报错信息全部一起抛出到控制台。
2、这样我们每次都需要等用例运行结束,才知道为什么报错,不方便实时查看报错信息。
3、 pytest-instafail 插件可以在运行用例的时候,可以实时查看用例报错内容,这样方便跟踪问题。【一旦遇到执行失败的测试用例,则控制台打印错误回溯信息;而不是等待全部测试用例完成执行后才统一抛出用例执行错误回溯信息】
# install
pip install pytest-instafail
# 结合 --tb=line 参数,看起来更直观:
忽略特定用例收集配置文件
1. 该 pytest-gitignore 插件会告诉pytest测试框架在收集测试用例时忽略收集任何 git 忽略的文件。【即.gitignore中忽略的文件,若文件中是测试用例则pytest测试框架会一并忽略】
2. 例如,如果将 test1.py文件保存在项目存放测试用例的目录中,则可以通过将它们放入 .gitignore来让 git 和pytest测试框架同时忽略它们。【git忽略其上传至git远程代码仓,pytest忽略在执行测试用例时收集该文件中的测试用例】
3. 要查看文件是否被 git 忽略,运行 git check-ignore(filename)
git忽略的文件不会影响pytest测试框架的对其中测试用例的收集和执行
# install
pip install pytest-gitignore
# 参数
--ignore="xx"
--ignore_glob="xxx"
链接 Mysql
# install
pip3 install pytest-mysql
发送网络请求
pip3 install pytest-requests
文件导入
pip3 install pytest-datafiles
# 主页: https://pypi.org/project/pytest-datafiles/
内置夹具 datafiles 值为一个保存复制有测试文件的临时目录,
# 使用示例:
@pytest.mark.datafiles(
"/Volumes/docu/MBP_Project001/pyAutoTest_Pro/pyTest_Area/pyt001/test_files/data01.txt",
"/Volumes/docu/MBP_Project001/pyAutoTest_Pro/pyTest_Area/pyt001/test_files/data02.txt",
"/Volumes/docu/MBP_Project001/pyAutoTest_Pro/pyTest_Area/pyt001/test_files/data03.txt",
)
def test_a(self, datafiles):
global kk; kk += 1
print(f"------->test_a = {myfix01} = {kk}")
subprocess.call(f"file {datafiles}", shell=True)
subprocess.call(f"ls -al {datafiles}", shell=True)
assert 1
# 运行结果
/private/var/folders/1_/gn5p26q90y1dqw6lg1vdwqvh0000gn/T/pytest-of-suosuo1930/pytest-19/test_a0: directory
total 24
drwx------ 5 suosuo1930 staff 160 Apr 15 01:46 .
drwx------ 5 suosuo1930 staff 160 Apr 15 01:46 ..
-rw-r--r-- 1 suosuo1930 staff 21 Apr 15 01:46 data01.txt
-rw-r--r-- 1 suosuo1930 staff 23 Apr 15 01:46 data02.txt
-rw-r--r-- 1 suosuo1930 staff 23 Apr 15 01:46 data03.txt
pytest-datadirs
# 主页: https://pypi.org/project/pytest-datadir/
缓存
# 注意:
1. lastfailed 文件中的大字典中最后一个键值对后不可以添加 ',' 号,其余的键值对后必须添加 ',' 号;
参考网址
Jenkins 攻略
Jenkins是一款开源 CI&CD 软件,用于自动化各种任务,包括构建、测试和部署软件。
Jenkins 支持各种运行方式,可通过系统包、Docker 或者通过一个独立的 Java 程序。
# 个人在 Rockey9.0 下安装并使用
# Jenkins 依赖 JDK环境,要求 JDK 17+
下载与安装
# Mac 下
brew install jenkins-lts
brew services start jenkins-lts
brew services restart jenkins-lts
brew upgrade jenkins-lts
Redhat 下安装
# 安装脚本
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
sudo dnf upgrade
# Add required dependencies for the jenkins package
sudo dnf install fontconfig java-21-openjdk
sudo dnf install jenkins
sudo systemctl daemon-reload
# JAVA 安装后配置
export JAVA_HOME=/usr/java/jdk8u352-b08
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib
export PATH=$JAVA_HOME/bin:$PATH
# 配置文件目录
/etc/java/java-21-openjdk/java-21-openjdk-21.0.2.0.13-1.el9.aarch64/conf
启动服务
# 通过 Jenkins War 包启动并安装
java -jar jenkins.war
Linux 下 Yum 安装并启动
# Yum 安装并启动服务
yum -y install jenkins; systemctl start jenkins.service
# 安装目录
/var/lib/jenkins
# 初始密码配置文件
/var/lib/jenkins/secrets/initialAdminPassword
容器 Docker 下启动 Jenkins
# 在主机下创建 Jenkins 挂载点并设置权限,
mkdir -p /shiwei/jenkins_home; chown -R 1000:1000 /shiwei/jenkins_homes
# 注意:
Jenkins 容器中 jenkins 用户的 UID 正好为 1000, 必须修改该权限,否则容器无法运行;
# 启动命令
docker run -di --name=jenkins -v /shiwei/jenkins_home:/var/jenkins_home -p 8080:8080 --privileged=true -v /usr/bin/docker:/bin/docker -v /var/run/docker.sock:/var/run/docker.sock jenkins/jenkins:lts
# 注意:
1. 使用 jenkins 镜像要 LTS 稳定版本: `jenkins/jenkins:lts` ; 默认是 latest 最新版本
2. 容器当中 jenkins 的安装路径为: `/var/jenkins_home`, 为了方便在容器外配置jenkins,故需挂载该目录;
3. jenkins 默认服务端口为 8080
Jenkins + Allure + Git 配置
1. 最新 Allure 插件版本可能存在 Bug 或是与当前版本Jenkins 不兼容, 若发现不能使用的话,则可以降低版本;
可从该插件官网下载低版本`allure-jenkins-plugin.hpi` 文件后再在 JenKins 中手动部署;
2.
# 项目 Item 默认目录
/var/jenkins_home/jobs
# 容器当中 jenkins 的安装路径为
/var/jenkins_home
Jenkins 插件 Allure 官网 hpi 文件下载地址
# QQ 邮箱 Jenkins 授权码
uqowpgeufzmacgjc
参考网址
Jenkins : Email-ext plugin --- 官网