一、项目介绍基于PythonpytestsqlalchemyrequestsallurejsonpathyamlJenkinsLinux该项目是一个在线购物的商城网站包括用户注册登录下单上架/下架商品下单支付等相关功能。二、项目结构说明pythonproject/ # 项目根目录 ├─ base/ # 基础类封装核心功能 │ ├─ apituil.py # 接口请求核心处理 │ ├─ apiutil_business.py # 业务场景接口处理 │ ├─ generateId.py # 编号生成器 │ ├─ new_testcase_tools.py # PyQt5 GUI工具 │ └─ removefile.py # 文件删除 ├─ common/ # 公共方法封装可复用工具 │ ├─ assertions.py # 断言模块 │ ├─ connection.py # 数据库连接 │ ├─ debugtalk.py # 辅助函数库 │ ├─ dingRobot.py # 钉钉机器人 │ ├─ handleExcel.py # Excel数据解析工具 │ ├─ operationcsv.py # CSV读取 │ ├─ operxml.py # XML读取 │ ├─ Pjenkins.py # Jenkins集成 │ ├─ readyaml.py # YAML数据解析工具 │ ├─ recordlog.py # 日志处理工具 │ ├─ semail.py # 邮件发送 │ ├─ sendrequest.py # HTTP请求封装requests │ └─ two_dimension_data.py # 表格打印 ├─ conf/ # 全局配置目录 │ ├─ config.ini # 环境配置文件API地址、账号等 │ ├─ operationConfig.py # 封装读取*.ini配置文件模块 │ └─ setting.py # 环境变量管理文件 ├─ data/ # 测试数据目录 │ ├─ sql/ # sql测试目录 │ │ ├─ homePage.xml # 查询企业销售数据 │ │ └─ newVehicleAddShare.xml # 查询新能源汽车新增量及市场份额 │ ├─ login_data.csv # 登录测试数据 │ ├─ loginName.yaml # 登录测试数据 │ ├─ 测试数据.xls # Excel测试数据 │ └─ vehicleNo.csv # 车牌数据 ├─ logs/ # 测试日志目录自动生成 │ └─ test_... # 按日期命名的日志文件 ├─ report/ # 测试报告目录 │ ├─ allureReport/ # Allure交互式报告自动生成 │ ├─ temp/ # JSON原始数据目录自动生成 │ └─ results.xml # JUnit XML格式的测试报告 ├─ testcase/ # 接口测试用例 │ ├─ Business interface/ # 业务场景 │ │ ├─ BusinessScenario.yml # 业务场景用例登录→商品列表→...→订单支付→校验状态 │ │ └─ test_business_scenario.py # 业务场景测试 │ ├─ProductManager/ # 商品管理用例 │ │ ├─ apiType.yaml # 测试接口状态查询 │ │ ├─ commitOrder.yaml # 测试提交订单接口 │ │ ├─ getProductList.yaml # 测试获取商品列表接口 │ │ ├─ login_dw.yaml # 测试电网系统登录接口 │ │ ├─ orderPay.yaml # 测试订单支付接口 │ │ ├─ productDetail.yaml # 测试商品详情接口 │ │ └─ test_productList.py # 调用上述 YAML执行商品管理相关接口测试 │ ├─Single interface/ # 单接口测试用户管理模块 │ │ ├─ addUser.yaml # 新增用户接口测试用例正常异常场景 │ │ ├─ deleteUser.yaml # 删除用户接口测试用例 │ │ ├─ queryUser.yaml # 查询用户接口测试用例 │ │ ├─ updateUser.yaml # 修改用户接口测试用例 │ │ └─ test_debug_api.py # 调用上述 YAML执行用户管理相关接口测试 │ └─conftest.py ├─ venv/ # 虚拟环境目录自动生成 ├─ conftest.py # pytest全局钩子文件固定名称 ├─ environment.xml # Allure报告环境信息文件 ├─ extract.yaml # 接口依赖参数存储文件 ├─ pytest.ini # pytest配置文件固定名称 ├─ requirements.txt # 第三方库依赖清单 └─ run.py # 主程序入口执行测试和生成报告三、核心代码1、程序入口代码如下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(fallure serve ./report/temp) elif REPORT_TYPE tm: pytest.main([-vs, --pytest-tmreport-nametestReport.html, --pytest-tmreport-path./report/tmreport]) webbrowser.open_new_tab(os.getcwd() /report/tmreport/testReport.html)代码说明if __name__ __main__:Python 标准写法表示当脚本作为主程序运行时才执行以下代码块。防止模块被其他脚本导入时意外执行测试逻辑。Allure 报告处理逻辑if REPORT_TYPE allure: pytest.main( [-s, -v, --alluredir./report/temp, ./testcase, --clean-alluredir, --junitxml./report/results.xml])参数如下参数含义-s输出所有打印信息不屏蔽 stdout-v显示详细测试结果--alluredir./report/temp将 Allure 报告数据保存到指定目录./testcase指定测试用例所在目录--clean-alluredir在每次运行前清空之前的报告数据--junitxml./report/results.xml生成 JUnit XML 格式的测试结果文件shutil.copy(./environment.xml, ./report/temp)复制环境信息文件environment.xml到报告目录中。Allure 报告会读取此文件并显示当前测试环境信息。os.system(fallure serve ./report/temp)使用 Allure CLI 命令启动本地服务器并展示生成的报告页面。会在默认浏览器中自动打开报告。2、测试用例 testcase商务管理代码示例import allure import pytest from common.readyaml import get_testcase_yaml from base.apiutil_business import RequestBase from base.generateId import m_id, c_id # 注意业务场景的接口测试要调用base目录下的apiutil_business文件 allure.feature(next(m_id) 电子商务管理系统业务场景) class TestEBusinessScenario: allure.story(next(c_id) 商品列表到下单支付流程) pytest.mark.parametrize(case_info, get_testcase_yaml(./testcase/Business interface/BusinessScenario.yml)) def test_business_scenario(self, case_info): allure.dynamic.title(case_info[baseInfo][api_name]) RequestBase().specification_yaml(case_info)代码说明pytest.mark.parametrize参数化装饰器pytest.mark.parametrize(case_info, get_testcase_yaml(./testcase/Business interface/BusinessScenario.yml))作用实现参数化测试即一个测试方法可以运行多组不同的输入数据。每一组 case_info 数据都会触发一次完整的测试执行。参数说明case_info表示每组测试数据的变量名在测试函数中作为参数使用。get_testcase_yaml(...)调用函数读取指定路径下的 YAML 文件内容返回一个包含多个测试用例的列表。示例yaml文件内容- baseInfo: api_name: 获取商品列表 method: GET url: /product/list - baseInfo: api_name: 提交订单 method: POST url: /order/create测试方法定义def test_business_scenario(self, case_info):这是一个pytest 测试方法每个参数化的case_info都会触发一次该方法的执行。self表示这是类中的一个实例方法属于TestEBusinessScenario类。case_info是从 YAML 文件中加载的一条测试用例的数据字典。动态设置 Allure 报告标题allure.dynamic.title(case_info[baseInfo][api_name])作用在生成的 Allure 报告中为当前测试用例设置一个可读性更强的标题。标题内容来自于 YAML 文件中baseInfo.api_name字段。示例效果如果api_name是获取商品列表那么在报告中显示的用例名称就是这个值。执行接口请求和校验RequestBase().specification_yaml(case_info)作用创建 RequestBase 实例并调用其 specification_yaml() 方法。该方法接收当前的测试用例数据 case_info并根据其中的配置如 URL、方法、请求头、预期结果等发送 HTTP 请求。同时会进行响应断言、日志记录等操作完成整个接口测试流程。3、接口测试specification_yaml()方法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, moderb)} res self.run.run_main(nameapi_name, urlurl, case_namecase_name, headerheader, methodmethod, filefiles, cookiescookie, **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 especification_yaml方法解析该方法用于处理 YAML 文件中定义的接口测试用例包括请求参数构造、动态变量替换、接口调用、响应断言以及数据提取等核心功能。步骤详解1. 定义常量与基础配置params_type [data, json, params] url_host self.conf.get_section_for_data(api_envi, host)params_type表示请求参数可能包含的类型字段。url_host从配置文件中获取当前环境的主机地址。2. 提取接口基本信息并添加 Allure 报告附件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中提取接口名称、URL 和请求方法。使用allure.attach将这些信息附加到 Allure 报告中便于测试结果查看。3. 处理请求头和 Cookieheader 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 进行变量替换。如果存在 cookies则同样进行替换并使用eval转换为字典格式。4. 提取测试用例名称并添加报告附件case_name test_case.pop(case_name) allure.attach(api_name, f测试用例名称{case_name}, allure.attachment_type.TEXT)从test_case中提取用例名称并删除原始字段。添加到 Allure 报告中。5. 处理断言逻辑val self.replace_load(test_case.get(validation)) test_case[validation] val validation eval(test_case.pop(validation))替换断言表达式中的变量。使用eval执行断言表达式生成实际断言规则6. 提取数据字段extract / extract_listextract test_case.pop(extract, None) extract_list test_case.pop(extract_list, None)从test_case中提取需要提取的字段名供后续从响应中提取数据7. 处理请求参数data /json/paramsfor key, value in test_case.items(): if key in params_type: test_case[key] self.replace_load(value)遍历所有测试用例参数若为data、json或params类型则对其值进行变量替换。8. 处理文件上传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, moderb)}如果存在files字段表示是文件上传接口。使用open(..., moderb)读取文件内容并附加到 Allure 报告中。9. 发送接口请求res self.run.run_main(nameapi_name, urlurl, case_namecase_name, headerheader, methodmethod, filefiles, cookiescookie, **test_case) status_code res.status_code allure.attach(self.allure_attach_response(res.json()), 接口响应信息, allure.attachment_type.TEXT)调用run_main方法发送请求。获取状态码和响应内容。将响应内容附加到 Allure 报告中。10. 处理响应与断言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将响应内容转为 JSON 字典。若有extract或extract_list则调用对应方法提取数据。使用断言方法对响应结果进行验证。11. 异常捕获except Exception as e: raise e捕获所有异常并重新抛出确保测试框架可以正确识别失败情况。示例说明假设 YAML 文件如下baseInfo: api_name: 登录接口 url: /login method: POST header: Content-Type: application/json cookies: null testCase: - case_name: 正常登录 json: username: admin password: ${get_password()} validation: code 200 and msg success extract: token: $.data.token经过specification_yaml处理后${get_password()}会被替换成实际密码。请求会携带正确的 JSON 数据。响应中的token会被提取保存。断言会检查是否返回预期结果。4.断言判断相等断言模式代码示例def equal_assert(self, expected_results, actual_results, status_codeNone): 相等断言模式 :param expected_results: 预期结果yaml文件validation值 :param actual_results: 接口实际响应结果 :return: flag 0 if isinstance(actual_results, dict) and isinstance(expected_results, dict): # 找出实际结果与预期结果共同的key common_keys list(expected_results.keys() actual_results.keys())[0] # 根据相同的key去实际结果中获取并重新生成一个实际结果的字典 new_actual_results {common_keys: actual_results[common_keys]} eq_assert operator.eq(new_actual_results, expected_results) if eq_assert: logs.info(f相等断言成功接口实际结果{new_actual_results}等于预期结果 str(expected_results)) allure.attach(f预期结果{str(expected_results)}\n实际结果{new_actual_results}, 相等断言结果成功, attachment_typeallure.attachment_type.TEXT) else: flag 1 logs.error(f相等断言失败接口实际结果{new_actual_results}不等于预期结果 str(expected_results)) allure.attach(f预期结果{str(expected_results)}\n实际结果{new_actual_results}, 相等断言结果失败, attachment_typeallure.attachment_type.TEXT) else: raise TypeError(相等断言--类型错误预期结果和接口实际响应结果必须为字典类型) return flag方法说明方法名称def equal_assert(self, expected_results, actual_results, status_codeNone)功能描述该方法用于实现相等断言Equal Assertion即验证接口返回的实际结果中某个字段的值是否完全等于预期值。通过对比两个字典对象实际结果与预期结果中的指定字段内容判断其是否完全一致。示例场景验证接口返回的code是否为200验证响应中的user.id是否等于预期值验证嵌套结构中的某个字段是否完全匹配预期值参数名类型描述expected_resultsdict预期结果通常来自 YAML 文件中的validation字段例如{code: 200}actual_resultsdict接口实际返回的 JSON 响应体statuc_codeint可选HTTP 状态码当前未使用且参数名存在拼写错误返回值说明返回一个整数类型flag0表示断言成功1表示断言失败执行逻辑详解1. 初始化标志位flag 0默认表示所有断言通过。2. 类型校验if isinstance(actual_results, dict) and isinstance(expected_results, dict):如果传入的actual_results和expected_results不是字典类型则抛出异常raise TypeError(相等断言--类型错误预期结果和接口实际响应结果必须为字典类型)3. 获取公共 key要比较的字段common_keys list(expected_results.keys() actual_results.keys())[0]使用集合运算符找出两个字典共有的 key。取第一个作为比较字段⚠️ 当前仅支持单字段比较4. 构造新的实际结果字典new_actual_results {common_keys: actual_results[common_keys]}从实际响应中提取对应字段的值构造一个新的字典。5. 使用 operator.eq 进行深度比较eq_assert operator.eq(new_actual_results, expected_results)operator.eq()是 Python 的深度比较函数可以比较复杂数据结构如嵌套字典、列表等是否完全相同。相比更加严格和可靠。6. 成功/失败处理成功时logs.info(相等断言成功...) allure.attach(...)记录日志并附加到 Allure 报告中。失败时flag 1 logs.error(相等断言失败...) allure.attach(...)标记失败记录错误日志并将断言信息附加到报告中包含断言模式代码示例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_typeallure.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_typeallure.attachment_type.TEXT) logs.error(响应文本断言失败预期结果为【%s】,实际结果为【%s】 % (assert_value, resp_list)) return flag方法说明def contains_assert(self, value, response, status_code)该方法用于实现字符串包含断言Contains Assertion即验证接口响应中是否包含预期的关键字段和值。支持对状态码和 JSON 响应内容进行断言。示例场景验证返回的msg是否包含登录成功验证 HTTP 状态码是否为200验证响应中的某个字段是否包含指定值参数说明参数名类型描述valuedict从 YAML 文件中读取的断言规则例如{ code: 200, msg: 登录成功 }responsedict接口实际返回的 JSON 响应体status_codeint接口返回的 HTTP 状态码返回值说明返回一个整数类型flag0表示所有断言通过大于0表示有断言失败数值表示失败次数执行逻辑详解1. 初始化标志位flag 0默认表示所有断言通过。2. 遍历断言字典for assert_key, assert_value in value.items():遍历传入的value字典获取每个断言字段及其预期值。3. 判断是否为状态码断言if assert_key status_code: if assert_value ! status_code: flag 1 allure.attach(...) # 添加失败信息到 Allure 报告 logs.error(...) # 记录错误日志如果是status_code则比较预期值与实际状态码。不相等时标记失败并记录日志和报告信息。4. 否则进行响应内容断言JSONPath 提取 包含判断resp_list jsonpath.jsonpath(response, $..%s % assert_key)使用jsonpath.jsonpath()从响应数据中提取所有名为assert_key的字段值。response {code: 200, data: {msg: 登录成功}} assert_key msg resp_list jsonpath.jsonpath(response, $..msg) → [登录成功]对结果做类型处理if isinstance(resp_list[0], str): resp_list .join(resp_list)如果提取到的是字符串列表合并成单个字符串方便后续判断判断是否为空值如 NONEassert_value None if assert_value.upper() NONE else assert_value支持将字符串NONE转换为 Python 的None便于后续判断空值。实际断言逻辑if assert_value in resp_list: logs.info(成功) else: flag 1 allure.attach(...) logs.error(失败)如果预期值存在于响应字段中则断言成功。否则标记失败记录日志并附加报告信息。四、项目结语本框架基于 pytest Allure 构建形成了一套成熟、稳定且具备良好扩展性的接口自动化测试体系可充分支撑电商业务场景的自动化验证。通过数据驱动、动态参数解析、多维度断言及可视化报告等核心机制显著提升了测试执行效率与质量保障能力。后续将持续推进框架的通用化适配与智能化能力建设。