遇到的问题:
登录token获取,只需要执行一次login,接口自动化框架中如何处理?
定义配置选项,用例执行过程中只需要配置一次,如何处理?
初始化数据库,构造测试数据,如何处理?
针对以上的问题,在实际项目实践过程中遇到2个坑:
1、用例之间有依赖关系,比如我上架商品之前需要新增商品,但是我把新增商品和上架商品分开为2个用例,这样就导致执行上架商品用例时,找不到商品,原因就是用例是在不同的执行机上执行的,新增商品用例的数据无法给上架商品用例使用
2、初始化的数据也会在不同的执行机上执行,比如我需要预先在数据库插入一条数据,这个数据是供所有的用例执行使用的。这时候就会出现报错Duplicate,原因就是不同的执行机都在执行插入数据库,导致数据重复了
就上面遇到的问题以及踩过的坑,我在这里跟大家分享一下我的处理过程和方案
pytest多进程执行原理
pytest-xdist的分布式类似于一主多从的结构,master负责下发命令,控制slave;slave根据master的命令执行特定测试任务。
在xdist中,主是master,从是workers;xdist会产生一个或多个workers,workers都通过master来控制,每个worker相当于一个mini版pytest执行器 。
master不执行测试任务,只对worker收集到的所有用例进行分发;每个worker负责执行测试用例,然后将执行结果反馈给master;由master统计最终测试结果。
登录token获取处理过程
在测试用例分布式执行时,每个测试用例的执行均需要进行token校验,这样就要求在执行完登录接口请求后所有的测试用例执行都可以获取到token。
首先想到的是把登录请求获取token的操作,放在conftest.py文件中,用@pytest.fixture(scope="session", autouse=True)装饰。但是运行后发现,部分接口偶然性会出现token鉴权失败的问题,抓取数据后发现token变化了。查找资料发现:不同的测试用例可能会调用同一个 scope 范围级别较高(例如session)的 fixture,该 fixture 则会被执行多次。
然后,尝试用多进程文件锁:FileLock。
import json
import random
from filelock import FileLock
@pytest.fixture(scope="session", autouse=True)
def initialized(tmp_path_factory, worker_id):
with allure.step("初始化展示分类"):
if worker_id == "master": #非分布式运行执行时走这里的逻辑
"""初始化数据库,接口请求,登录操作等代码"""
data = str(random.random())
# 如果测试用例有需要,可以返回对应的数据,比如 token
print(f"master首次执行,数据是{data} ")
return data
# 如果是分布式运行
# 获取所有子节点共享的临时目录,无需修改【不可删除、修改】
root_tmp_dir = tmp_path_factory.getbasetemp().parent
# 【不可删除、修改】
fn = root_tmp_dir / "data.json"
# 【不可删除、修改】
with FileLock(str(fn) + ".lock"):
# 【不可删除、修改】
if fn.is_file():
# 缓存文件中读取数据,像登录操作的话就是token 【不可删除、修改】
data = json.loads(fn.read_text())
print(f"worker读取缓存文件,数据是{data} ")
return data
else:
"""初始化数据库,接口请求,登录操作等代码"""
data = str(random.random())
# 【不可删除、修改】
fn.write_text(json.dumps(data))
print(f"worker首次执行,数据是{data} ")
return data
对于上面的多进程文件锁写法,写一下我自己的理解:
if worker_id == "master": #非分布式运行执行时走这里的逻辑
"""初始化数据库,接口请求,登录操作等代码"""
data = str(random.random())
# 如果测试用例有需要,可以返回对应的数据,比如 token
print(f"master首次执行,数据是{data} ")
return data
非分布式运行时,和分布式运行时第一次执行时的代码逻辑保持一样。比如:data = str(random.random()),类似于获取token,初始化数据库等。
# 获取所有子节点共享的临时目录,无需修改【不可删除、修改】
root_tmp_dir = tmp_path_factory.getbasetemp().parent
这里的不可删除、修改,指的是格式是按照这个格式,代码也是照抄,不要改动。这里代码的作用是获取分布式执行临时文件目录地址,目录地址是自动生成的,所以这里不要改动和删除,固定写法。
# 【不可删除、修改】
fn = root_tmp_dir / "data.json"
这里的不可删除、修改,指的是格式不能删除和修改。这里代码的作用是获取缓存文件的路径(即.json文件的路径),后缀名是固定写法,但是data可以变,比如我有多个fixture需要有多个缓存文件的时候,如果都写成data.json,就会存在后面的缓存数据覆盖前面缓存数据的情况。所以这里可以用data1.json,或者data2.json等来区分不同的缓存文件。
# 【不可删除、修改】
with FileLock(str(fn) + ".lock"):
这里的不可删除、修改,指的是格式和内容都是固定写法,代表多进程文件锁。
# 【不可删除、修改】
if fn.is_file():
这里的不可删除、修改,指的是格式和内容都是固定写法,代表如果多进程文件锁存在,则执行下面的代码逻辑,读取缓存文件的数据,不需要再次执行请求登录操作等
# 缓存文件中读取数据,像登录操作的话就是token 【不可删除、修改】
data = json.loads(fn.read_text())
return data
这里的不可删除、修改,指的是格式是固定写法,必须要写读取缓存文件获取数据的代码。这里可以写多个获取数据的代码,也可以改变读取缓存文件的方式。
比如:
with open(fn, mode='a+', encoding='utf-8') as f:
postId = int(json.loads(f.readline()))
appId = int(json.loads(f.readline()))
apptoken = json.loads(f.readline())
return apptoken, postId, appId
# 【不可删除、修改】
fn.write_text(json.dumps(data))
print(f"worker首次执行,数据是{data} ")
return data
这里的不可删除、修改,指的是格式是固定写法,需要将数据写入缓存文件的代码。这里可以写多个写入环境文件的代码,也可以改变写入缓存文件的方式。
比如:
with open(fn, mode='a+', encoding='utf-8') as f:
# 【不可删除、修改】
f.write(json.dumps(postId) + '\n')
f.write(json.dumps(appId) + '\n')
f.write(json.dumps(apptoken) + '\n')
return apptoken, postId, appId
json的用法解释:
json.loads()将str类型的数据转换为dict类型
json.dumps()将dict类型的数据转成str
然后使用多进程文件锁后,发现2个测试用例,总是有一个是失败的。排查发现有一个用例能获取到token,但是另一个执行器上面的用例获取不到token。具体原因还不是很清楚,期待有人能帮忙解释一下,或者后续研究。于是,我又尝试通过 --dist 参数来控制执行顺序。
pytest-xdist 默认是无序执行的。
--dist=loadscope:将按照同一个模块 module 下的函数和同一个测试类 class 下的方法来分组,然后将每个测试组发给可以执行的 worker,确保同一个组的测试用例在同一个进程中执行。目前无法自定义分组,按类 class 分组优先于按模块 module 分组。
设置后,再次运行多次,没有再出现用例执行失败的情况。问题得到解决!
框架中分布式执行设置
在框架pyproject.toml文件中设置运行模式:
# 设置运行参数
addopts = "-sv -n auto --dist=loadscope --env uat --mode local --reruns=2 --reruns-delay=2 --alluredir ./tmp/allure-results --clean-alluredir"
参数解释:
-n auto 并行测试
--dist=loadscope:将按照同一个模块 module 下的函数和同一个测试类 class 下的方法来分组,然后将每个测试组发给可以执行的 worker,确保同一个组的测试用例在同一个进程中执行。目前无法自定义分组,按类 class 分组优先于按模块 module 分组。
--reruns=2 重试2次
--reruns-delay=2 重试等待时间2s
--alluredir ./tmp/allure-results 生成allure存储路径
--clean-alluredir 清空历史报告
接口框架分布式执行FileLock相关问题定位步骤:
1、查看allure报告,确定暂存tmp_path_factory的位置
2、查看tmp_path_factory中data.json的数据,确定是否是缓存的数据,以及缓存的数据是否正确
3、开启fiddler,抓取请求数据,查看参数是否有获取到对应的值。如果没有需要确定是写缓存文件的问题,还是读取缓存文件的问题。
至此,感谢您的阅读,希望能帮助你解决遇到的问题。
好文链接
发表评论