Allure进阶
- 一、Allure进阶
-
- [1. 标题后面的参数隐藏及标题动态展示](#1. 标题后面的参数隐藏及标题动态展示)
-
- [1)在接口对应的 py 文件中添加 allure 标题展示](#1)在接口对应的 py 文件中添加 allure 标题展示)
- [2)修改 allure 里的 listener.py 文件](#2)修改 allure 里的 listener.py 文件)
- [2. 显示 allure 报告的用例顺序](#2. 显示 allure 报告的用例顺序)
- 二、pytest进阶
一、Allure进阶
1. 标题后面的参数隐藏及标题动态展示
目标:将yaml文件中的测试用例名动态展示到 allure 中
- 未修改前
yaml 文件内容为
yaml
test_case:
- case_name: 用户名和密码正确登录验证
json:
username: "{{get_data(username, login_data.csv)}}" # 注意:传了 filename
password: "{{get_data(password, login_data.csv)}}"
validation:
- contains: {code: "200"}
- contains: {message: "成功"}
extract:
token: '"token":"(.*?)"'
1)在接口对应的 py 文件中添加 allure 标题展示

选择 allure.dynamic.title

代码展示
python
import pytest
import allure
from common.run_yaml import ReadYamlData
from conf.setting import FILE_PATH
from base.apiutil import BaseRequest
@allure.feature("登录接口")
class TestLogin:
readyaml = ReadYamlData()
@allure.story("正确登录")
@pytest.mark.parametrize('case_info', readyaml.read(FILE_PATH['login']))
def test_case01(self, case_info):
allure.dynamic.title(case_info['test_case'][0]['case_name'])
BaseRequest().specification_yaml(case_info)
修改之后,标题展示了,但后面参数占位,需隐藏参数

2)修改 allure 里的 listener.py 文件
若找不到文件位置,cmd输入以下语句
cmd
cd C:\
dir /s listener.py
找到文件位置

双击打开文件

文件中查找 test_result.parameters.extend 此处为参数展示

将内容置空,变为 [ ] 空列表

再次运行成功隐藏参数并正确展示 yaml 标题

2. 显示 allure 报告的用例顺序
最终效果

1)模块编号方法
目标: 生成一系列格式为 M01_, M02_, M03_, ..., M99_ 的"模块编号"
在 base 下创建 generateId.py 文件

编写生成模块编号方法
python
def generate_module_id():
"""
生产测试模块编号,为了保证allure报告的顺序与pytest设定的执行顺序保持一致
:return:
"""
for i in range(1,100):
module_id = 'M' + str(i).zfill(2) + '_' # zfill(2) 表示将数字补零到 2 位,例如:1 → '01', 10 → '10'
yield module_id # yield可以按序生成,return每次只生成一个
m_id = generate_module_id()
- zfill(2) 表示将数字补零到 2 位,例如:1 → '01', 10 → '10'
- yield可以按序生成,return每次只生成一个
结果展示:

2)用例编号方法
目标: 生成测试用例编号,格式为 C01_, C02_, ..., C99_
在 generateId.py 文件中继续编写方法

编写生成用例编号方法
python
def generate_testcase_id():
"""
生成测试用例编号
:return:
"""
for i in range(1,100):
case_id = 'C' + str(i).zfill(2) + '_'
yield case_id
c_id = generate_testcase_id()
结果展示:

3)模块及用例编号方法使用
在接口运行文件中使用 c_id ,m_id 方法
引入 base.generateId 中的 c_id , m_id 方法
python
from base.generateId import c_id,m_id
使用模块编号
python
@allure.feature(next(m_id) + "登录接口")
使用用例编号
python
@allure.story(next(c_id) + "正确登录")
运行文件中整体代码展示
python
import pytest
import allure
from common.run_yaml import ReadYamlData
from conf.setting import FILE_PATH
from base.apiutil import BaseRequest
from base.generateId import c_id,m_id
@allure.feature(next(m_id) + "登录接口")
class TestLogin:
readyaml = ReadYamlData()
@allure.story(next(c_id) + "正确登录")
@pytest.mark.parametrize('case_info', readyaml.read(FILE_PATH['login']))
def test_case01(self, case_info):
allure.dynamic.title(case_info['test_case'][0]['case_name'])
BaseRequest().specification_yaml(case_info)
@allure.story(next(c_id) + "错误登录")
@pytest.mark.parametrize('case_info', readyaml.read(FILE_PATH['login']))
def test_case02(self, case_info):
allure.dynamic.title(case_info['test_case'][0]['case_name'])
BaseRequest().specification_yaml(case_info)
最终结果展示:

二、pytest进阶
1. 文件上传接口
注意:文件上传接口一般不需要请求头
yaml
testCase:
- case_name:导入车辆黑名单文件
files:
file: ./data/heimingdan online.xlsx
在 apiutil.py 文件 读取yaml测试用例方法 新增内容 - - - 以二进制方式读取文件内容
python
# 处理文件上传接口
file,files = to.pop('files',None),None # to为yaml数据 test_case 的拆分
if file is not None:
for fk,fv in file.items():
allure.attach(json.dumps(file), '导入文件')
files = {fk:open(fv,mode='rb')} # 以二进制的方式读取文件内容
res = self.send.run_main(name=api_name, url=url, case_name=case_name,
method=method, headers=headers, cookies=cookie,
file=files, **to)
2. 单接口多条测试用例
1)前置条件
yaml 文件,单接口包含多条测试用例
python
- baseInfo:
api_name: 用户登录
url: /api/basic/auth/loginWithoutCheckCode
method: POST
header:
Content-Type: application/json
cookies:
session: hjsksksjj17272
test_case:
- case_name: 用户名和密码正确登录验证
json:
username: "{{get_data(username, login_data.csv)}}" # 注意:传了 filename
password: "{{get_data(password, login_data.csv)}}"
validation:
- contains: {code: "200"}
- contains: {message: "成功"}
extract:
token: '"token":"(.*?)"'
- case_name: 用户名和密码异常登录验证
json:
username: "{{get_data(username, login_data.csv)}}" # 注意:传了 filename
password: "{{get_data(password, login_data.csv)}}"
validation:
- contains: { code: "200" }
- contains: { message: "成功" }
extract:
token: '"token":"(.*?)"'
2)分别读取YAML文件 baseInfo,test_case
避免重复写 URL、header、method 等公共信息,提高 YAML 可维护性

将yaml文件的 baseInfo,test_case 分别读取出来
python
def read(self, yaml_file):
"""读取 YAML 文件,返回 [(base_info, test_case), ...] 列表用于参数化"""
if not os.path.exists(yaml_file):
raise FileNotFoundError(f"配置文件不存在: {yaml_file}")
with open(yaml_file, 'r', encoding='utf-8') as f:
try:
datas = yaml.safe_load(f)
if not datas:
raise ValueError("YAML 文件为空")
# 假设 datas 是一个列表,每个元素是一个接口
testcase_list = []
for item in datas:
base_info = item.get('baseInfo')
test_cases = item.get('test_case') # 注意:与 YAML 一致
if not base_info or not test_cases:
raise ValueError(f"YAML 格式错误:缺少 baseInfo 或 test_case")
for case in test_cases:
testcase_list.append((base_info, case))
return testcase_list
except yaml.YAMLError as e:
raise ValueError(f"YAML 解析错误: {e}")
except Exception as e:
raise ValueError(f"读取 YAML 失败: {e}")
3)拆分使用 baseInfo,test_case

在单接口运行文件中添加 baseInfo,test_case 参数
python
@allure.feature(next(m_id) + "登录接口")
class TestLogin:
readyaml = ReadYamlData()
@allure.story(next(c_id) + "正确登录")
@pytest.mark.parametrize('base_info,test_case', readyaml.read(FILE_PATH['login']))
def test_case01(self, base_info,test_case):
allure.dynamic.title(test_case['case_name'])
BaseRequest().specification_yaml(base_info,test_case)
- @pytest.mark.parametrize 实现了"一接口多用例"的核心;
- allure.dynamic.title() 动态设置用例标题,避免所有用例都叫 test_case01
- next(m_id) 和 next(c_id) 是自定义的编号生成器
编号生成器

python
def generate_module_id():
"""
生产测试模块编号,为了保证allure报告的顺序与pytest设定的执行顺序保持一致
:return:
"""
for i in range(1,100):
module_id = 'M' + str(i).zfill(2) + '_' # zfill(2) 表示将数字补零到 2 位,例如:1 → '01', 10 → '10'
yield module_id # yield可以按序生成,return每次只生成一个
def generate_testcase_id():
"""
生成测试用例编号
:return:
"""
for i in range(1,100):
case_id = 'C' + str(i).zfill(2) + '_'
yield case_id
m_id = generate_module_id()
c_id = generate_testcase_id()
4)修改接口文件

修改接口文件 baseInfo,test_case 读取
python
def specification_yaml(self,base_info,test_case):
"""
接口请求处理基本方法
:param base_info: yaml文件里的baseInfo
:param test_case: yaml文件里的testCase
:return:
"""
try:
params_type = ['params', 'json', 'data']
cookie = None
"""获取接口的基础数据"""
base_url = self.conf.get_envi('host')
url = urljoin(f"{base_url}", base_info['url'])
allure.attach(url, f'接口地址:{url}')
api_name = base_info['api_name']
allure.attach(api_name, f'接口名称:{api_name}')
method = base_info['method']
allure.attach(method, f'请求方法:{method}')
# 先取出 headers,再替换
headers = base_info['header']
headers = self.replace_load(headers)
allure.attach(str(headers), f'请求头:{headers}', allure.attachment_type.TEXT)
try:
cookie = base_info.get('cookies')
if cookie:
cookie = self.replace_load(cookie)
cookie_str = json.dumps(cookie, ensure_ascii=False) if isinstance(cookie,dict) else str(cookie)
allure.attach(cookie_str, 'Cookie信息', allure.attachment_type.TEXT)
except Exception as e:
logs.warning(f"处理cookie失败: {e}")
"""获取该接口的所有测试用例"""
case_name = test_case.pop('case_name') # 把case_name删除
allure.attach(case_name, f'测试用例名称:{case_name}')
# 处理断言
val = self.replace_load(test_case.get('validation'))
test_case['validation'] = val
validation = test_case.pop('validation') # 把validation删除
# 处理参数提取
extract = test_case.pop('extract', None)
extract_list = test_case.pop('extract_list', None) # 把extract_list删除
# 处理接口的请求参数
for key, value in test_case.items():
if key in params_type:
test_case[key] = self.replace_load(value) # 解析value
# 处理文件上传接口
file,files = test_case.pop('files',None),None
if file is not None:
for fk,fv in file.items():
allure.attach(json.dumps(file), '导入文件')
files = {fk:open(fv,mode='rb')} # 以二进制的方式读取文件内容
res = self.send.run_main(name=api_name, url=url, case_name=case_name,
method=method, headers=headers, cookies=cookie,
file=files, **test_case)
allure.attach(res.text, f'接口的响应信息:{res}', allure.attachment_type.TEXT)
res_json = res.json()
if extract is not None:
self.extract_data(extract,res.text)
if extract_list is not None:
self.extract_data_list(extract_list,res.text)
# 处理接口断言
self.assert_res.assert_result(validation,res_json,res.status_code)
except Exception as e:
logs.error(e)
raise e
- replace_load 是用于解析 {{get_data(...)}} 这类动态表达式的函数(如正则替换 + CSV 读取))