一、项目介绍
基于Python+pytest+sqlalchemy+requests+allure+jsonpath+yaml+Jenkins+Linux
基于Python+pytest+sqlalchemy+requests+allure+jsonpath+yaml+Jenkins+Linux技术栈搭建的泰和昌商城接口自动化测试项目,针对在线购物商城的核心业务流程开展接口自动化验证。
该项目覆盖泰和昌商城用户注册、登录、商品查询、加入购物车、下单支付、商品上架/下架、订单管理等全流程接口,通过自动化测试实现接口功能的快速校验、回归测试与持续集成,保障商城接口的稳定性与正确性。
二、项目结构说明
css
pythonproject/ # 项目根目录
├── base/ # 基础功能封装目录
│ ├── __init__.py # 包初始化文件
│ ├── apiutil.py # 接口请求工具类(封装requests)
│ ├── apiutil_business.py # 业务层接口封装(组合接口逻辑)
│ ├── generateId.py # 唯一ID生成工具(如订单号、商品ID)
│ ├── new_testcase_tools.py # 测试用例辅助工具(数据构造、参数化)
│ ├── new_tools.ui # 可视化工具界面(可选)
│ └── removefile.py # 文件清理工具(日志/报告清理)
├── common/ # 公共方法封装目录
│ ├── __init__.py # 包初始化文件
│ ├── assertions.py # 断言工具类(自定义断言规则)
│ ├── connection.py # 数据库连接封装(SQLAlchemy)
│ ├── debugtalk.py # 调试工具(接口调试、数据校验)
│ ├── dingRobot.py # 钉钉机器人通知(测试结果推送)
│ ├── handleExcel.py # Excel数据解析工具
│ ├── operationscsv.py # CSV文件操作工具
│ ├── operxml.py # XML文件解析工具
│ ├── Pjenkins.py # Jenkins交互工具(任务触发、报告获取)
│ ├── readyaml.py # YAML配置解析工具
│ ├── recordlog.py # 日志记录工具(封装logging)
│ ├── semail.py # 邮件发送工具(测试报告推送)
│ ├── sendrequest.py # 接口请求发送封装
│ └── two_dimension_data.py # 二维数据处理工具
├── conf/ # 全局配置目录
│ ├── __init__.py # 包初始化文件
│ ├── config.ini # 环境配置文件(API地址、数据库信息)
│ ├── operationConfig.py # 配置文件操作类
│ └── setting.py # 全局常量配置(如超时时间、报告路径)
├── data/ # 测试数据目录
│ ├── __init__.py # 包初始化文件
│ ├── sql/ # SQL脚本目录(测试数据初始化/清理)
│ ├── login_data.csv # 登录接口CSV测试数据
│ ├── loginName.yaml # 登录用户名YAML配置
│ ├── vehicleNo.csv # 车辆编号测试数据(业务相关)
│ └── 测试数据.xls # 通用业务Excel测试数据
├── logs/ # 测试日志目录(自动生成)
│ └── test_20260107.log # 按日期命名的日志文件
├── report/ # 测试报告目录(自动生成)
│ ├── allureReport/ # Allure交互式报告目录
│ ├── temp/ # 报告临时文件目录
│ ├── tmreport/ # TMReport表格报告目录
│ └── results.xml # 测试结果XML文件
├── testcase/ # 测试用例目录
│ ├── Business interface/ # 业务场景测试用例
│ │ ├── BusinessScenario.yml # 业务场景YAML配置
│ │ └── test_business_scenario.py # 业务场景测试类
│ ├── ProductManager/ # 商品管理接口测试
│ │ ├── apiType.yml # 接口类型YAML配置
│ │ ├── commitOrder.yml # 下单接口YAML配置
│ │ ├── getProductList.yml # 商品列表接口YAML配置
│ │ ├── login_dw.yml # 登录接口YAML配置
│ │ ├── orderPay.yml # 订单支付接口YAML配置
│ │ ├── productDetail.yml # 商品详情接口YAML配置
│ │ └── test_productList.py # 商品列表接口测试类
│ ├── Single interface/ # 单接口测试用例
│ │ ├── addUser.yml # 新增用户接口YAML配置
│ │ ├── deleteUser.yml # 删除用户接口YAML配置
│ │ ├── queryUser.yml # 查询用户接口YAML配置
│ │ ├── test_debug_api.py # 接口调试测试类
│ │ └── updateUser.yml # 更新用户接口YAML配置
│ ├── __init__.py # 包初始化文件
│ └── conftest.py # pytest全局钩子文件
├── venv/ # 虚拟环境目录(自动生成)
├── __init__.py # 项目根包初始化文件
├── conftest.py # 全局pytest钩子文件
├── environment.xml # Allure报告环境信息文件
├── extract.yaml # 接口依赖参数存储文件(如token、订单号)
├── pytest.ini # pytest配置文件(运行参数、插件配置)
├── requirements.txt # 第三方库依赖清单(如pytest、requests)
└── run.py # 主程序入口(执行测试、生成报告)
三、核心代码
1.程序入口 run.py
代码如下:
python
import shutil
import pytest
import os
import webbrowser
from conf.setting import REPORT_TYPE
if __name__ == '__main__':
if REPORT_TYPE == 'allure':
pytest.main(
['-s', '-v', '--alluredir=./report/temp', './testcase', '--clean-alluredir',
'--junitxml=./report/results.xml'])
shutil.copy('./environment.xml', './report/temp')
os.system(f'allure serve ./report/temp')
elif REPORT_TYPE == 'tm':
pytest.main(['-vs', '--pytest-tmreport-name=testReport.html', '--pytest-tmreport-path=./report/tmreport'])
webbrowser.open_new_tab(os.getcwd() + '/report/tmreport/testReport.html')
代码说明:
程序主入口
python
if __name__ == '__main__':
- Python程序入口判断语句,区分脚本运行方式(作为主程序运行时执行代码;作为模块导入时不执行代码)
- 是用来触发自动化测试的主流程(执行pytest用例、生成Allure报告等)
- 保证该模块被导入时不会自动执行测试流程
Allure报告生成
python
if REPORT_TYPE == 'allure':
pytest.main(
['-s', '-v', '--alluredir=./report/temp', './testcase', '--clean-alluredir',
'--junitxml=./report/results.xml'])
|---------------------------------|------------------------|
| 参数 | 说明 |
| -s | 输出所有打印信息 |
| -v | 显示详细测试结果 |
| ---alluredir=./report/temp | 将 Allure 报告数据保存到指定目录 |
| ./testcase | 指定测试用例所在目录 |
| --clean-alluredir | 在每次运行前清空之前的报告数据 |
| --junitxml=./report/results.xml | 生成 JUnit XML 格式的测试结果文件 |
环境读取
python
shutil.copy('./environment.xml', './report/temp')
- 复制环境信息文件
environment.xml到报告目录中。 - Allure 报告会读取此文件并显示当前测试环境信息。
报告展示
- 使用 Allure CLI 命令启动本地服务器并展示生成的报告页面。
- 会在默认浏览器中自动打开报告。
2.testcase(测试用例)
商品管理代码示例:
python
import allure
import pytest
from base.generateId import m_id, c_id
from base.apiutil import RequestBase
from common.readyaml import get_testcase_yaml
@allure.feature(next(m_id) + '商品管理(单接口)')
class TestLogin:
@allure.story(next(c_id) + "获取商品列表")
@pytest.mark.run(order=1)
@pytest.mark.parametrize('base_info,testcase', get_testcase_yaml('./testcase/ProductManager/getProductList.yaml'))
def test_get_product_list(self, base_info, testcase):
allure.dynamic.title(testcase['case_name'])
RequestBase().specification_yaml(base_info, testcase)
@allure.story(next(c_id) + "获取商品详情信息")
@pytest.mark.run(order=2)
@pytest.mark.parametrize('base_info,testcase', get_testcase_yaml('./testcase/ProductManager/productDetail.yaml'))
def test_get_product_detail(self, base_info, testcase):
allure.dynamic.title(testcase['case_name'])
RequestBase().specification_yaml(base_info, testcase)
@allure.story(next(c_id) + "提交订单")
@pytest.mark.run(order=3)
@pytest.mark.parametrize('base_info,testcase', get_testcase_yaml('./testcase/ProductManager/commitOrder.yaml'))
def test_commit_order(self, base_info, testcase):
allure.dynamic.title(testcase['case_name'])
RequestBase().specification_yaml(base_info, testcase)
@allure.story(next(c_id) + "订单支付")
@pytest.mark.run(order=4)
@pytest.mark.parametrize('base_info,testcase', get_testcase_yaml('./testcase/ProductManager/orderPay.yaml'))
def test_order_pay(self, base_info, testcase):
allure.dynamic.title(testcase['case_name'])
RequestBase().specification_yaml(base_info, testcase)
(1)@pytest.mark.run(order=N) 执行顺序装饰器
python
@pytest.mark.run(order=1)
作用:
- 指定测试用例的执行顺序, N 为数字(数字越小,执行优先级越高)。
- 在商品管理接口测试中,用于保证"获取商品列表→获取商品详情→提交订单→订单支付"的业务流程按真实场景顺序执行。
(2)@pytest.mark.parametrize() 参数化装饰器
python
@pytest.mark.parametrize('base_info,testcase', get_testcase_yaml('./testcase/ProductManager/getProductList.yaml'))
作用:
- 实现参数化测试,一个测试方法可运行多组不同的输入数据;
- 每组 base_info 和 testcase 数据都会触发一次完整的测试执行,无需为不同用例编写重复代码。
参数说明:
- 'base_info, testcase' :测试函数的参数名,分别接收接口基础配置(如API地址、请求方法)和测试用例数据(如入参、预期结果);
- get_testcase_yaml(...) :调用自定义函数读取指定YAML文件内容,返回包含多组测试用例的列表,为参数化提供数据来源。
示例(YAML文件内容):
XML
- baseInfo:
api_name: 商品列表
url: /coupApply/cms/goodsList
method: Get
header:
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
token: ${get_extract_data(cookie)}
testCase:
- case_name: 获取商品列表
params:
msgType: getHandsetListOfCust
page: 1
size: 20
validation:
- contains: { 'error_code': '0000' }
extract_list:
# token: '"token": "(.*?)"'
goodsId: $.goodsList[*].goodsId
(3)@allure.story() & allure.dynamic.title() Allure报告装饰器
python
@allure.story(next(c_id) + "获取商品详情信息")
作用:
- @allure.story() :为测试用例添加业务场景标签(如"获取商品列表""提交订单"),在Allure报告中按场景分类展示;
- allure.dynamic.title() :动态设置测试用例的标题(取自YAML中的 case_name ),让报告中用例名称更直观。
(4)RequestBase().specification_yaml() 接口请求核心方法
python
RequestBase().specification_yaml(base_info, testcase)
作用:
接收 base_info 和 testcase 参数,按YAML配置完成接口请求的发送、响应结果的校验(如状态码、返回数据断言),是接口自动化的核心执行逻辑。
核心测试用例函数说明:
测试方法定义
python
def test_get_product_list(self, base_info, testcase):
- 这是一个pytest测试方法,每个参数化的 base_info 和 testcase 组合数据,都会触发一次该方法的执行。
- self 表示这是类中的一个实例方法(属于测试类)。
- base_info 是从YAML文件中加载的接口基础配置字典(包含API地址、请求方法、请求头等核心配置)。
- testcase 是从YAML文件中加载的单条测试用例的业务数据字典(包含接口入参、预期响应结果、断言规则等)。
|-------------------------|------------|---------|---------------------|
| 函数名 | 功能 | 执行顺序 | 数据来源 |
| test_get_product_list | 测试获取商品列表接口 | order=1 | getProductList.yaml |
| test_get_product_detail | 测试获取商品详情接口 | order=2 | productDetail.yaml |
| test_commit_order | 测试提交订单接口 | order=3 | commitOrder.yaml |
| test_order_pay | 测试订单支付接口 | order=4 | orderPay.yaml |
(5)Allure报告标题动态设置
python
allure.dynamic.title(testcase['case_name'])
作用:
- 在生成的 Allure 报告中,为当前测试用例设置一个可读性更强的标题。
- 标题内容来自于 YAML 文件中 case_name (获取商品列表)字段。
3.接口测试 specification_yaml
代码示例
python
def specification_yaml(self, base_info, test_case):
"""
接口请求处理基本方法
:param base_info: yaml文件里面的baseInfo
:param test_case: yaml文件里面的testCase
:return:
"""
try:
params_type = ['data', 'json', 'params']
url_host = self.conf.get_section_for_data('api_envi', 'host')
api_name = base_info['api_name']
allure.attach(api_name, f'接口名称:{api_name}', allure.attachment_type.TEXT)
url = url_host + base_info['url']
allure.attach(api_name, f'接口地址:{url}', allure.attachment_type.TEXT)
method = base_info['method']
allure.attach(api_name, f'请求方法:{method}', allure.attachment_type.TEXT)
header = self.replace_load(base_info['header'])
allure.attach(api_name, f'请求头:{header}', allure.attachment_type.TEXT)
# 处理cookie
cookie = None
if base_info.get('cookies') is not None:
cookie = eval(self.replace_load(base_info['cookies']))
case_name = test_case.pop('case_name')
allure.attach(api_name, f'测试用例名称:{case_name}', allure.attachment_type.TEXT)
# 处理断言
val = self.replace_load(test_case.get('validation'))
test_case['validation'] = val
validation = eval(test_case.pop('validation'))
# 处理参数提取
extract = test_case.pop('extract', None)
extract_list = test_case.pop('extract_list', None)
# 处理接口的请求参数
for key, value in test_case.items():
if key in params_type:
test_case[key] = self.replace_load(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.run.run_main(name=api_name, url=url, case_name=case_name, header=header, method=method,
file=files, cookies=cookie, **test_case)
status_code = res.status_code
allure.attach(self.allure_attach_response(res.json()), '接口响应信息', allure.attachment_type.TEXT)
try:
res_json = json.loads(res.text) # 把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.asserts.assert_result(validation, res_json, status_code)
except JSONDecodeError as js:
logs.error('系统异常或接口未请求!')
raise js
except Exception as e:
logs.error(e)
raise e
except Exception as e:
raise e
specification_yaml()方法说明
该方法是接口自动化的核心处理方法,接收接口基础配置和测试用例数据,完成从请求构造到响应断言的全流程处理。
方法定义
python
def specification_yaml(self, base_info, test_case):
"""
接口请求处理基本方法
:param base_info: yaml文件里面的baseInfo
:param test_case: yaml文件里面的testCase
:return:
"""
- base_info 为从YAML文件读取的接口基础信息字典,包含接口名称、URL、请求方法等核心配置; test_case 为从YAML文件读取的测试用例数据字典,包含入参、断言规则、数据提取规则等。
- 方法无显式返回值,通过断言校验接口响应是否符合预期,断言失败则抛出异常,标记测试用例失败。
详细说明
(1)定义常量与基础配置
python
params_type = ['data', 'json', 'params']
url_host = self.conf.get_section_for_data('api_envi', 'host')
- params_type :定义请求参数的常见类型字段( data / json / params ),用于后续识别并处理不同类型的接口入参。
- url_host :从配置文件中读取当前测试环境的主机地址(如测试环境/生产环境的域名),实现环境配置的解耦。
(2)提取接口核心信息并附加到Allure报告
python
api_name = base_info['api_name']
allure.attach(api_name, f'接口名称: {api_name}', allure.attachment_type.TEXT)
url = url_host + base_info['url']
allure.attach(api_name, f'接口地址: {url}', allure.attachment_type.TEXT)
method = base_info['method']
allure.attach(api_name, f'请求方法: {method}', allure.attachment_type.TEXT)
- 从 base_info 中提取接口名称、接口路径、请求方法,拼接成完整的接口地址。
- 通过 allure.attach 将这些核心信息附加到Allure报告中,便于测试结果分析时快速查看接口基础信息。
(3)处理请求头和Cookie
python
header = self.replace_load(base_info['header'])
allure.attach(api_name, f'请求头: {header}', allure.attachment_type.TEXT)
cookie = None
if base_info.get('cookies') is not None:
cookie = eval(self.replace_load(base_info['cookies']))
- 调用 replace_load 方法对请求头( header )进行动态变量替换(如替换token、时间戳等动态参数)。
- 若 base_info 中配置了 cookies ,先执行变量替换,再通过 eval 转换为字典格式,用于接口请求的Cookie认证。
- 将处理后的请求头信息附加到Allure报告,便于调试接口认证问题。
(4)提取测试用例名称并附加到报告
python
case_name = test_case.pop('case_name')
allure.attach(api_name, f'测试用例名称: {case_name}', allure.attachment_type.TEXT)
- 通过 test_case.pop('case_name') 提取用例名称并从字典中移除该字段,避免后续参数处理时产生干扰。
- 将用例名称附加到Allure报告,使报告中能清晰识别每个测试用例的业务场景。
(5) 处理断言规则与动态变量
python
val = self.replace_load(test_case.get('validation'))
test_case['validation'] = val
validation = eval(test_case.pop('validation'))
- 对 test_case 中的断言规则( validation )执行动态变量替换,支持基于前置接口返回值的动态断言。
- 通过 eval 将断言规则的字符串转换为可执行的Python对象(如字典、列表),为后续接口响应断言做准备。
(6)处理参数提取规则
python
extract = test_case.pop('extract', None)
extract_list = test_case.pop('extract_list', None)
- 提取 test_case 中的数据提取规则( extract / extract_list ),用于从接口响应中提取关键数据(如token、订单号),供后续接口请求复用。
- 若未配置提取规则,默认赋值为 None ,不影响后续流程执行。
(7)处理接口请求参数
python
for key, value in test_case.items():
if key in params_type:
test_case[key] = self.replace_load(value)
- 遍历 test_case 中的参数,若参数类型属于 params_type ( data / json / params ),则执行动态变量替换,支持动态入参(如使用前置接口提取的订单号)。
(8)处理文件上传接口
python
file, files = test_case.pop('files', None), None
if file is not None:
for fk, fv in file.items():
allure.attach(json.dumps(file), '导入文件', allure.attachment_type.TEXT)
files = {fk: open(fv, mode='rb')}
- 若测试用例配置了文件上传参数( files ),先将文件信息附加到Allure报告,再通过 open 以二进制模式打开文件,构造文件上传的请求参数。
(9)发送接口请求并获取响应
python
res = self.run.run_main(name=api_name, url=url, case_name=case_name, header=header, method=method, file=files, cookies=cookie,** test_case)
status_code = res.status_code
allure.attach(self.allure_attach_response(res.json()), '接口响应信息', allure.attachment_type.TEXT)
- 调用 run_main 方法发送接口请求,传入处理后的请求头、Cookie、参数、文件等信息。
- 获取接口响应状态码,并将响应数据格式化后附加到Allure报告,便于调试接口返回结果。
(10)数据提取与接口断言
python
res_json = json.loads(res.text)
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.asserts.assert_result(validation, res_json, status_code)
- 将接口响应的文本数据转换为字典格式( res_json ),方便数据提取和断言操作。
- 若配置了数据提取规则,调用 extract_data / extract_data_list 从响应中提取关键数据并存储,供后续接口使用。
- 调用 assert_result 方法,根据断言规则校验接口响应的状态码、返回数据是否符合预期,断言失败则抛出异常。
(11)异常处理
python
except JSONDecodeError as js:
logs.error('系统异常或接口未请求!')
raise js
except Exception as e:
logs.error(f'接口请求处理失败: {str(e)}')
raise e
- 捕获 JSONDecodeError (响应数据非JSON格式),输出错误日志并重新抛出异常,标记用例失败。
- 捕获其他通用异常,记录详细错误信息并抛出,确保测试用例的异常能被pytest捕获并在报告中展示。
4.断言判断
包含断言
python
def contains_assert(self, value, response, status_code):
"""
字符串包含断言模式,断言预期结果的字符串是否包含在接口的响应信息中
:param value: 预期结果,yaml文件的预期结果值
:param response: 接口实际响应结果
:param status_code: 响应状态码
:return: 返回结果的状态标识
"""
# 断言状态标识,0成功,其他失败
flag = 0
for assert_key, assert_value in value.items():
if assert_key == "status_code":
if assert_value != status_code:
flag += 1
allure.attach(f"预期结果:{assert_value}\n实际结果:{status_code}", '响应代码断言结果:失败',
attachment_type=allure.attachment_type.TEXT)
logs.error("contains断言失败:接口返回码【%s】不等于【%s】" % (status_code, assert_value))
else:
resp_list = jsonpath.jsonpath(response, "$..%s" % assert_key)
if isinstance(resp_list[0], str):
resp_list = ''.join(resp_list)
if resp_list:
assert_value = None if assert_value.upper() == 'NONE' else assert_value
if assert_value in resp_list:
logs.info("字符串包含断言成功:预期结果【%s】,实际结果【%s】" % (assert_value, resp_list))
else:
flag = flag + 1
allure.attach(f"预期结果:{assert_value}\n实际结果:{resp_list}", '响应文本断言结果:失败',
attachment_type=allure.attachment_type.TEXT)
logs.error("响应文本断言失败:预期结果为【%s】,实际结果为【%s】" % (assert_value, resp_list))
return flag
方法说明
方法定义
python
def contains_assert(self, value, response, status_code):
"""
字符串包含断言模式,断言预期结果的字符串是否包含在接口的响应信息中
:param value: 预期结果,yaml文件的预期结果值
:param response: 接口实际响应结果
:param status_code: 响应状态码
:return: 返回结果的状态标识(0为成功,非0为失败)
"""
- 该方法是字符串包含型断言工具,主要用于校验接口响应状态码是否符合预期,以及响应文本中是否包含指定的字符串内容。
- 入参 value 为YAML配置的断言规则字典, response 为接口实际返回的响应数据, status_code 为接口响应状态码;返回值为状态标识, 0 代表断言成功,非 0 代表断言失败。
核心逻辑详解
(1)初始化断言状态标识
python
flag = 0 # 断言状态标识,0成功,其他失败
- 定义 flag 变量作为断言结果的标识,初始值为 0 (默认断言成功),若任意断言项失败, flag 会累加变为非0值。
(2)遍历断言规则执行校验
python
for assert_key, assert_value in value.items():
- 遍历 value 字典中的断言规则(键为断言类型,值为预期结果),支持同时校验多个断言项(如同时校验状态码和响应文本)。
(3) 响应状态码断言
python
if assert_key == "status_code":
if assert_value != status_code:
flag += 1
allure.attach(f"预期结果: {assert_value}\n实际结果: {status_code}", "响应代码断言结果:失败", attachment_type=allure.attachment_type.TEXT)
logs.error("contains断言失败: 接口返回码【%s】不等于【%s】" % (status_code, assert_value)
- 当断言键为 status_code 时,校验接口实际响应状态码是否与预期值一致。
- 若不一致, flag 加1,通过 allure.attach 将断言失败信息附加到Allure报告,同时通过日志模块记录错误信息,便于问题排查。
(4) 响应文本包含断言
python
else:
resp_list = jsonpath.jsonpath(response, "$..%s" % assert_key)
if isinstance(resp_list[0], str):
resp_list = ''.join(resp_list)
if resp_list:
assert_value = None if assert_value.upper() == 'NONE' else assert_value
if assert_value in resp_list:
logs.info("字符串包含断言成功: 预期结果【%s】,实际结果【%s】" % (assert_value, resp_list))
else:
flag += 1
allure.attach(f"预期结果: {assert_value}\n实际结果: {resp_list}", "响应文本断言结果:失败", attachment_type=allure.attachment_type.TEXT)
logs.error("响应文本断言失败: 预期结果为【%s】,实际结果为【%s】" % (assert_value, resp_list))
- JSONPath提取响应数据:通过 jsonpath.jsonpath 从接口响应 response 中,按 assert_key 对应的JSON路径提取目标数据,支持多层嵌套的JSON数据解析。
- 字符串格式化:若提取的结果为字符串类型,通过 ''.join(resp_list) 将列表转换为字符串,便于后续包含校验。
- 特殊值处理:若预期值为 NONE (不区分大小写),将 assert_value 赋值为 None ,支持"响应数据为空"的断言场景。
- 包含校验:判断预期值是否包含在提取的响应数据中,若包含则记录成功日志;若不包含则 flag 加1,附加失败信息到Allure报告并记录错误日志
方法核心价值
-
多维度断言:同时支持状态码和响应文本的断言,满足接口自动化中最常见的两类校验需求。
-
可视化报告:断言失败时将预期/实际结果附加到Allure报告,直观展示失败原因,提升调试效率。
-
灵活的JSON解析:基于JSONPath提取响应数据,支持复杂嵌套的JSON结构解析,适配各类接口的响应格式。
断言接口响应信息中的body的任何属性值
python
def assert_result(self, expected, response, status_code):
"""
断言,通过断言all_flag标记,all_flag==0表示测试通过,否则为失败
:param expected: 预期结果
:param response: 实际响应结果
:param status_code: 响应code码
:return:
"""
all_flag = 0
try:
logs.info("yaml文件预期结果:%s" % expected)
# logs.info("实际结果:%s" % response)
# all_flag = 0
for yq in expected:
for key, value in yq.items():
if key == "contains":
flag = self.contains_assert(value, response, status_code)
all_flag = all_flag + flag
elif key == "eq":
flag = self.equal_assert(value, response)
all_flag = all_flag + flag
elif key == 'ne':
flag = self.not_equal_assert(value, response)
all_flag = all_flag + flag
elif key == 'rv':
flag = self.assert_response_any(actual_results=response, expected_results=value)
all_flag = all_flag + flag
elif key == 'db':
flag = self.assert_mysql_data(value)
all_flag = all_flag + flag
else:
logs.error("不支持此种断言方式")
except Exception as exceptions:
logs.error('接口断言异常,请检查yaml预期结果值是否正确填写!')
raise exceptions
if all_flag == 0:
logs.info("测试成功")
assert True
else:
logs.error("测试失败")
assert False
方法说明
方法定义
python
def assert_result(self, expected, response, status_code):
"""
统一断言入口方法,根据断言类型调用不同的断言函数,汇总断言结果
:param expected: YAML配置的断言规则列表(包含多种断言类型)
:param response: 接口实际响应结果
:param status_code: 接口响应状态码
:return: 无显式返回值,通过assert True/False标记测试用例成败
"""
- 该方法是接口自动化的统一断言入口,支持根据YAML配置的断言类型(包含、相等、不等、任意属性、数据库校验等),自动调用对应的断言方法,并汇总所有断言结果,最终通过 assert 关键字标记测试用例的成败。
- 是断言体系的"总控方法",整合了各类细分断言逻辑,简化了测试用例中断言的调用方式。
核心逻辑详解
(1)初始化总断言状态标识
python
all_flag = 0 # 总断言标识,0表示所有断言通过,非0表示存在断言失败
- 定义 all_flag 作为所有断言项的结果汇总标识,初始值为 0 ,每有一个断言项失败,该值会累加对应断言方法返回的非0值。
(2) 异常捕获与断言规则遍历
python
try:
logs.info(f'yaml文件预期结果: {expected}')
for yq in expected: # 遍历断言规则列表中的每一组断言配置
for key, value in yq.items(): # 遍历单组断言的类型与规则
# 根据断言类型调用对应断言方法
if key == "contains":
flag = self.contains_assert(value, response, status_code)
all_flag = all_flag + flag
elif key == "eq":
flag = self.equal_assert(value, response)
all_flag = all_flag + flag
elif key == "ne":
flag = self.not_equal_assert(value, response)
all_flag = all_flag + flag
elif key == "rv":
flag = self.assert_response_any(actual_results=response, expected_results=value)
all_flag = all_flag + flag
elif key == "db":
flag = self.assert_mysql_data(value)
all_flag = all_flag + flag
else:
logs.error("不支持此种断言方式")
except Exception as exceptions:
logs.error('接口断言异常,请检查yaml预期结果值是否正确填写!')
raise exceptions
- 日志记录:打印YAML配置的预期断言规则,便于调试时核对断言配置。
- 双层遍历断言规则:
外层 for yq in expected 遍历断言规则列表(支持多组断言配置);
内层 for key, value in yq.items() 遍历单组断言的类型(key)与规则(value)。
- 按类型调用断言方法:
断言类型(key) 调用方法 断言功能
contains contains_assert 字符串包含断言+状态码断言
eq equal_assert 等值断言(实际=预期)
ne not_equal_assert 不等值断言(实际≠预期)
rv assert_response_any 响应任意属性值断言
db assert_mysql_data 数据库数据校验断言
- 不支持的断言类型:若配置了未定义的断言类型,记录错误日志,不影响其他断言项执行。
- 异常处理:捕获断言过程中的所有异常(如配置格式错误、断言方法调用失败等),记录错误日志并重新抛出异常,确保异常能被pytest捕获。
(3)断言结果最终校验
python
if all_flag == 0:
logs.info("测试成功")
assert True
else:
logs.error("测试失败")
assert False
- 若 all_flag 为 0 ,说明所有断言项均通过,记录成功日志并执行 assert True ,标记测试用例成功。
- 若 all_flag 非 0 ,说明存在断言失败项,记录失败日志并执行 assert False ,标记测试用例失败。
四、项目结语
本项目基于pytest+Allure技术栈搭建了泰和昌商城接口自动化测试框架,覆盖商品管理、订单支付、用户操作等电商核心业务的接口测试场景。框架通过数据驱动解耦测试用例与测试数据,借助动态变量替换适配接口的动态参数需求,依托多维度断言体系实现接口响应的精准校验,结合Allure完成测试报告的可视化输出,有效提升了商城接口测试的效率与回归测试的覆盖度。同时,框架支持与Jenkins集成实现持续测试,具备良好的可扩展性与维护性。未来将进一步集成接口性能监控等功能,持续提升框架的智能化与全链路测试能力,为泰和昌商城的系统稳定性提供更全面的测试保障。