Pytest+requests进行接口自动化测试8.0(Allure进阶 + 文件上传接口 + 单接口多用例)

Allure进阶

一、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 读取))
相关推荐
川石课堂软件测试10 小时前
CSS中常用的几种定位。
开发语言·css·python·网络协议·http·html·pytest
啊森要自信1 天前
【GUI自动化测试】Python 自动化测试框架 pytest 全面指南:基础语法、核心特性(参数化 / Fixture)及项目实操
开发语言·python·ui·单元测试·pytest
啊森要自信2 天前
【GUI自动化测试】YAML 配置文件应用:从语法解析到 Python 读写
android·python·缓存·pytest·pip·dash
我的xiaodoujiao2 天前
从 0 到 1 搭建完整 Python 语言 Web UI自动化测试学习系列 17--测试框架Pytest基础 1--介绍使用
python·学习·测试工具·pytest
程序员杰哥3 天前
Pytest与Unittest测试框架对比
自动化测试·软件测试·python·测试工具·测试用例·excel·pytest
软件测试小仙女3 天前
Pytest参数化实战:高效测试API接口
软件测试·测试开发·测试工具·pytest·接口测试·api·参数化
子正3 天前
Pytest单元测试一例:u16采样值格式转换的错误
单元测试·pytest
cllsse4 天前
pytest学习
软件测试·python·pytest
Test.X5 天前
学习16天:pytest学习
学习·pytest