乐购商城接口自动化项目架构介绍
一、项目介绍基于PythonpytestsqlalchemyrequestsallurejsonpathyamlJenkinsLinux该项目是一个在线购物的商城网站包括用户注册登录下单上架/下架商品下单支付等相关功能。二、项目结构说明pythonproject-root/ # 项目根目录 ├─ base/ # 基础类封装核心功能、测试用例工具 │ └─ new_testcase_tools.py # 测试用例工具类 ├─ common/ # 公共方法封装 ├─ conf/ # 存放全局配置文件目录 │ ├─ config.ini # 环境配置文件 ├─ data/ # 存放测试数据路径 ├─ logs/ # 存放测试日志目录 │ └─ test.20251009.log # 按日期命名的日志文件 ├─ report/ # 测试报告生成目录目前支持生成两种形式的报告 │ ├─ allureReport # Allure交互式报告 │ └─ tmreport # TMReport表格报告 ├─ testcase/ #存放测试用例文件目录 ├─ venv/ # 本框架使用的虚拟环境 ├─ conftest.py # 全局操作固定名称 ├─ environment.xml # allure测试报告总览-环境显示内容 ├─ extract.yaml # 接口依赖参数存放文件 ├─ pytest.ini # pytest框架规范约束 ├─ requirements.txt # 本框架所使用的到的第三方库 └─ run.py # 主程序入口三、核心代码1. 程序入口 run.py代码如下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: 商品详情 url: /coupApply/cms/productDetail method: post header: Content-Type: application/json;charsetUTF-8测试方法定义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 文件中定义的接口测试用例包括请求参数构造、动态变量替换、接口调用、响应断言以及数据提取等核心功能。方法定义def specification_yaml(self, base_info, test_case): 接口请求处理基本方法 :param base_info: yaml文件里面的baseInfo接口基本信息 :param test_case: yaml文件里面的testCase测试用例数据 :return: 无返回值但通过断言验证接口行为 步骤详解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捕获所有异常并重新抛出确保测试框架可以正确识别失败情况。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(...)标记失败记录错误日志并将断言信息附加到报告中四.测试报告Allure报告部分截图测试套分布测试涵盖了三个主要的测试套分别是testcase.Single interface单接口测试套、testcase.Business interface业务接口测试套和testcase.ProductManager产品管理测试套。各测试套的用例均全部通过说明不同类型的接口及业务场景相关功能都能正常工作。总结本次基于 Allure 生成的测试报告从多方面展示了测试成果。100% 的通过率以及各测试场景、接口的良好表现可以充分说明所测试系统的功能稳定和接口健壮。同时Allure 报告清晰的结构和丰富的信息也为后续测试分析、问题定位及项目质量评估提供了有力支持。