一、项目介绍

    本项目是一套基于 Python + Pytest 构建的企业级物流系统接口自动化测试框架。针对物流业务链路长、状态流转复杂(如:订单->仓储->运输->结算)的特点,框架采用了 “混合驱动(关键字驱动 + 数据驱动)” 的设计模式。

    技术栈涵盖 Requests (通信)、YAML (数据分离)、SQLAlchemy (数据库断言)、Allure (可视化报告) 及 Jenkins (CI/CD集成)。项目旨在解决回归测试成本高、接口依赖处理繁琐的痛点,实现了核心业务场景的 100% 自动化覆盖,显著提升了版本迭代的交付质量与效率。

二、项目结构设计

    本项目采用经典的 PO (Page Object) 分层架构思想,将测试数据、业务逻辑、底层封装与配置环境彻底解耦,由以下核心模块组成:

  • Common (公共组件层):核心底层封装。包含 SendRequest (统一请求/异常处理)、DBUtil (SQLAlchemy 数据库交互)、LogUtil (日志监控) 及 AssertUtil (多维断言策略)。

  • Data (数据驱动层):基于 YAML 管理测试数据。实现测试数据与代码分离,支持多环境(Dev/Test/Prod)配置切换及动态参数注入。

  • TestCase (业务测试层):基于 Pytest 编写。利用 Fixture 实现前后置处理(如自动登录鉴权),覆盖物流下单、轨迹查询、运单调度等核心业务闭环。

  • Conf (配置管理层):全局环境配置。管理 Host、数据库连接串、账号信息等,支持一键切换测试环境。

  • Report (可视化报告):集成 Allure。生成包含请求耗时、详细日志、错误堆栈的交互式 HTML 报告,并对接 Jenkins 实现邮件自动发送。

  • 主要目录结构如下:

    Logistics_API_AutoTest/
    ├── common/             # [公共封装层] 框架的基石
    │   ├── apiutil.py      # 业务中枢:处理参数替换、依赖提取、流程调度
    │   ├── sendrequest.py  # 请求引擎:二次封装 Requests,统一处理 Log/Cookie/Exception
    │   ├── readyaml.py     # 数据引擎:YAML 读取与动态写入 (接口依赖透传)
    │   ├── connect_db.py   # 数据校验:基于 SQLAlchemy 的数据库交互封装
    │   └── assertions.py   # 断言组件:封装相等、包含、数据库等多维断言
    ├── conf/               # [配置层] 环境与全局变量
    │   ├── setting.py      # 项目路径、日志级别等常量配置
    │   └── config.ini      # 区分环境的配置 (URL, DB User/Pwd)
    ├── data/               # [数据层] 测试用例数据仓库
    │   └── business_scenario/ # 存放订单、仓储等业务场景的 YAML 文件
    ├── testcase/           # [测试用例层] Pytest 脚本
    │   ├── conftest.py     # Pytest 共享 Fixture (自动登录、Token 全局共享)
    │   └── test_order.py   # 具体业务测试脚本
    ├── report/             # [报告层] 测试结果产出
    │   ├── temp/           # Allure 原始 JSON 数据
    │   └── html/           # 生成的最终 HTML 报告
    ├── requirements.txt    # 项目依赖库列表
    └── run.py              # 框架统一执行入口

三、核心代码解析

1. 程序入口 run.py(测试执行与报告生成)

作为框架的主入口,支持灵活切换测试环境、指定用例范围、生成多种格式测试报告,核心代码如下:

import shutil       # 用于文件操作,这里用来复制 environment.xml
import pytest       # 测试框架核心
import os           # 用于执行系统命令(如 allure serve)和获取路径
import webbrowser   # 用于自动打开浏览器
from conf.setting import REPORT_TYPE  # 从配置文件夹读取报告类型变量

if __name__ == '__main__':
    if REPORT_TYPE == 'allure':
        # 1. 执行 Pytest 测试
        pytest.main(
            ['-s', '-v', 
             '--alluredir=./report/temp',   # 指定生成 Allure 原始数据(JSON/TXT)的临时目录
             './testcase',                  # 指定要运行的测试用例目录
             '--clean-alluredir',           # 重要!运行前清空 temp 目录,防止旧数据干扰
             '--junitxml=./report/results.xml'] # 生成标准的 JUnit XML 报告(给Jenkins用的)
        )

        # 2. 复制环境配置文件
        # 把 environment.xml 复制到 temp 目录。
        # 作用:让 Allure 报告首页显示 "Environment" 信息(如测试地址、系统版本等)
        shutil.copy('./environment.xml', './report/temp')

        # 3. 启动本地 Allure 服务并打开报告
        # os.system 会调用命令行。'allure serve' 会启动一个临时的 Jetty Web 服务
        # 并自动在默认浏览器中打开渲染好的 HTML 报告。
        os.system(f'allure serve ./report/temp')

    elif REPORT_TYPE == 'tm':
        # 1. 执行测试并生成 HTML 文件
        pytest.main([
            '-vs', 
            '--pytest-tmreport-name=testReport.html',  # 指定报告文件名
            '--pytest-tmreport-path=./report/tmreport' # 指定报告存放路径
        ])
        
        # 2. 使用 Python 自带库直接在浏览器打开生成的 HTML 文件
        webbrowser.open_new_tab(os.getcwd() + '/report/tmreport/testReport.html')

2.数据驱动与接口依赖自动关联

    为了应对物流系统复杂的业务流转(如:创建订单 -> 获取订单号 -> 申请运单 -> 运单调度),框架构建了强大的数据处理引擎:

  • 数据与逻辑分离:采用 YAML 管理测试用例,通过 readyaml.py 动态加载数据,实现了“一套代码,多组数据”的各种边界测试(如空参、非法字符、超长文本)。

  • 依赖自动传递

    • 提取 (Extract):封装 extract_data 方法,支持通过正则表达式或 JsonPath 从上游接口响应中提取关键参数(如 token, order_id)并持久化至临时文件。

    • 注入 (Inject):在请求发送前,通过 replace_load 方法扫描 ${var} 占位符,自动从上下文中读取并替换真实值,实现了业务链路的自动化闭环。

    为了降低测试人员编写 YAML 的门槛,避免格式错误,使用了一个可视化用例生成工具。 测试人员只需在界面输入 URL、参数和预期结果,点击生成,即可自动产出符合框架规范的 .yaml 文件。这使得用例编写效率提升了 50% 以上。

配套 YAML 测试数据(commitOrder.yaml):

- baseInfo:
    api_name: 提交订单
    url: /coupApply/cms/placeAnOrder
    method: post
    header:
      Content-Type: application/json;charset=UTF-8
  testCase:
    - case_name: 提交订单
      json:
        goods_id: ${get_extract_data(goodsId,0)}
        number: 2
        propertyChildIds: '2:9'
        inviter_id: '127839112'
        price: '128'
        freight_insurance: '0.0'
        discount_code: '002399'
        consignee_info: {"name": "张三","phone": 13800000000,"address": "北京市海淀区西三环北路74号院4栋3单元1008"}
      validation:
        - eq: { 'message': '提交订单成功' }
        - eq: {'error_code':'0000'}
      extract:
        orderNumber: $.orderNumber
        userId: $.userId

3. 核心请求基类(apiutil.py)

     本框架的核心竞争力在于对底层通用逻辑的深度封装。 通过将 HTTP 请求发送 (SendRequest) 与 业务逻辑处理 (RequestBase) 分离,我们实现了:

  1. 请求统一化:屏蔽 GET/POST 差异,统一处理 Header 与 Cookie。

  2. 数据动态化:支持 ${func()} 形式的参数热替换。

  3. 流程标准化:固化了 请求 -> 提取 -> 断言 的自动化闭环。

以下是实现这些功能的关键代码方法:

  1. run_main (统一请求入口)
    作为“门面”,统一接收所有请求参数,负责日志记录、Allure 步骤挂载以及异常拦截。
    def run_main(self, name, url, case_name, header, method, cookies=None, file=None, **kwargs):
            """
            接口请求
            :param name: 接口名
            :param url: 接口地址
            :param case_name: 测试用例
            :param header:请求头
            :param method:请求方法
            :param cookies:默认为空
            :param file: 上传文件接口
            :param kwargs: 请求参数,根据yaml文件的参数类型
            :return:
            """
    
            try:
                # 1.【日志记录】收集报告日志,详细记录接口的所有入参
                logs.info('接口名称:%s' % name)
                logs.info('请求地址:%s' % url)
                logs.info('请求方式:%s' % method)
                logs.info('测试用例名称:%s' % case_name)
                logs.info('请求头:%s' % header)
                logs.info('Cookie:%s' % cookies)
                
                # 2.【Allure 步骤挂载】将请求参数挂载到测试报告中
                req_params = json.dumps(kwargs, ensure_ascii=False)
                if "data" in kwargs.keys():
                    allure.attach(req_params, '请求参数', allure.attachment_type.TEXT)
                    logs.info("请求参数:%s" % kwargs)
                elif "json" in kwargs.keys():
                    allure.attach(req_params, '请求参数', allure.attachment_type.TEXT)
                    logs.info("请求参数:%s" % kwargs)
                elif "params" in kwargs.keys():
                    allure.attach(req_params, '请求参数', allure.attachment_type.TEXT)
                    logs.info("请求参数:%s" % kwargs)
                    
            # 3.【异常拦截】捕获处理参数解析或日志记录时的异常,防止非请求错误导致程序中断
            except Exception as e:
                logs.error(e)
                
            # 4.【统一分发】调用底层的 send_request 进行真正的网络发送
            # time.sleep(0.5)
            requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
            response = self.send_request(method=method,
                                         url=url,
                                         headers=header,
                                         cookies=cookies,
                                         files=file,
                                         timeout=setting.API_TIMEOUT,
                                         verify=False,
                                         **kwargs)
            return response
  2. specification_yaml (测试执行中枢)
    解析 YAML 结构,按顺序调度参数处理、请求发送、结果提取和断言校验,是单个用例运行的“指挥官”。
    def specification_yaml(self, base_info, test_case):
            """
            接口请求处理基本方法
            :param base_info: yaml文件里面的baseInfo
            :param test_case: yaml文件里面的testCase
            :return:
            """
            try:
                # 1.【参数解析与预处理】拼装URL,处理请求头、Cookie,并记录到Allure报告
                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)
                
                # 【关键点】调用 replace_load 进行动态参数替换(如 ${token})
                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)
                
                # 循环处理请求体参数(data/json/params),进行动态替换
                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')}
    
                # 2.【请求发送】调用 SendRequest.run_main 发送请求
                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格式转换成字典字典
                    
                    # 3.【依赖提取】如果有 extract 规则,从响应中提取数据并存入 extract.yaml
                    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)
                        
                    # 4.【断言校验】对比预期结果 validation 和实际结果 res_json
                    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
  3. replace_load (反射机制热替换)
    作用:利用 Python 反射 (getattr),动态执行辅助函数(如加密、生成随机数),解决静态 YAML 数据无法处理动态参数的问题。
        def replace_load(self, data):
            """yaml数据替换解析"""
            str_data = data
            if not isinstance(data, str):
                str_data = json.dumps(data, ensure_ascii=False)
                # print('从yaml文件获取的原始数据:', str_data)
            for i in range(str_data.count('${')):
                if '${' in str_data and '}' in str_data:
                    start_index = str_data.index('$')
                    end_index = str_data.index('}', start_index)
                    ref_all_params = str_data[start_index:end_index + 1]
                    # 取出yaml文件的函数名
                    func_name = ref_all_params[2:ref_all_params.index("(")]
                    # 取出函数里面的参数
                    func_params = ref_all_params[ref_all_params.index("(") + 1:ref_all_params.index(")")]
                    # 传入替换的参数获取对应的值,类的反射----getattr,setattr,del....
                    extract_data = getattr(DebugTalk(), func_name)(*func_params.split(',') if func_params else "")
    
                    if extract_data and isinstance(extract_data, list):
                        extract_data = ','.join(e for e in extract_data)
                    str_data = str_data.replace(ref_all_params, str(extract_data))
                    # print('通过解析后替换的数据:', str_data)
    
            # 还原数据
            if data and isinstance(data, dict):
                data = json.loads(str_data)
            else:
                data = str_data
            return data

  4. extract_data (接口依赖自动提取)
    作用:基于 JsonPath 和正则表达式,从响应中提取关键数据(如 Token)并持久化到临时文件,实现接口间的数据传递。
    def extract_data(self, testcase_extarct, response):
            """
            提取接口的返回值,支持正则表达式和json提取器
            :param testcase_extarct: testcase文件yaml中的extract值
            :param response: 接口的实际返回值
            :return:
            """
            try:
                pattern_lst = ['(.*?)', '(.+?)', r'(\d)', r'(\d*)']
                for key, value in testcase_extarct.items():
    
                    # 处理正则表达式提取
                    for pat in pattern_lst:
                        if pat in value:
                            ext_lst = re.search(value, response)
                            if pat in [r'(\d+)', r'(\d*)']:
                                extract_data = {key: int(ext_lst.group(1))}
                            else:
                                extract_data = {key: ext_lst.group(1)}
                            self.read.write_yaml_data(extract_data)
                    # 处理json提取参数
                    if '$' in value:
                        ext_json = jsonpath.jsonpath(json.loads(response), value)[0]
                        if ext_json:
                            extarct_data = {key: ext_json}
                            logs.info('提取接口的返回值:', extarct_data)
                        else:
                            extarct_data = {key: '未提取到数据,请检查接口返回值是否为空!'}
                        self.read.write_yaml_data(extarct_data)
            except Exception as e:
                logs.error(e)

    replace_load“输入端” 的处理器(在请求发出去之前,把参数里的占位符换成真值)。extract_data“输出端” 的处理器(在请求回来之后,把结果里的关键值存下来供后面用)。它们共同存在于 RequestBase 这个业务中枢类中。

4. 多维度断言组件 (assertions.py)

      为了应对物流系统接口复杂的校验需求(如订单状态流转、库存扣减检查),框架摒弃了硬编码的断言方式。 我封装了一个独立的断言工具类,它采用策略模式的设计思想,能够根据 YAML 用例中定义的 validation 规则(如 equalcontains),动态选择对应的断言方法。

  • 解耦:测试人员写 YAML 时不需要关心代码逻辑,只需要填 eq, contains 等关键字。

  • 全面:覆盖了状态码、文本包含、精确相等、数据库校验、性能校验等几乎所有测试场景。

  • 清晰:断言失败时,会通过 allure.attach 把“预期值”和“实际值”都贴到报告里,排查问题一目了然。

代码实现如下:

import traceback
import allure
import jsonpath
import operator

from common.recordlog import logs
from common.connection import ConnectMysql


class Assertions:
    """"
    接口断言模式,支持
    1)响应文本字符串包含模式断言
    2)响应结果相等断言
    3)响应结果不相等断言
    4)响应结果任意值断言
    5)数据库断言

    """

    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

    def equal_assert(self, expected_results, actual_results, statuc_code=None):
        """
        相等断言模式
        :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_type=allure.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_type=allure.attachment_type.TEXT)
        else:
            raise TypeError('相等断言--类型错误,预期结果和接口实际响应结果必须为字典类型!')
        return flag

    def not_equal_assert(self, expected_results, actual_results, statuc_code=None):
        """
        不相等断言模式
        :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.ne(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_type=allure.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_type=allure.attachment_type.TEXT)
        else:
            raise TypeError('不相等断言--类型错误,预期结果和接口实际响应结果必须为字典类型!')
        return flag

    def assert_response_any(self, actual_results, expected_results):
        """
        断言接口响应信息中的body的任何属性值
        :param actual_results: 接口实际响应信息
        :param expected_results: 预期结果,在接口返回值的任意值
        :return: 返回标识,0表示测试通过,非0则测试失败
        """
        flag = 0
        try:
            exp_key = list(expected_results.keys())[0]
            if exp_key in actual_results:
                act_value = actual_results[exp_key]
                rv_assert = operator.eq(act_value, list(expected_results.values())[0])
                if rv_assert:
                    logs.info("响应结果任意值断言成功")
                else:
                    flag += 1
                    logs.error("响应结果任意值断言失败")
        except Exception as e:
            logs.error(e)
            raise
        return flag

    def assert_response_time(self, res_time, exp_time):
        """
        通过断言接口的响应时间与期望时间对比,接口响应时间小于预期时间则为通过
        :param res_time: 接口的响应时间
        :param exp_time: 预期的响应时间
        :return:
        """
        try:
            assert res_time < exp_time
            return True
        except Exception as e:
            logs.error('接口响应时间[%ss]大于预期时间[%ss]' % (res_time, exp_time))
            raise

    def assert_mysql_data(self, expected_results):
        """
        数据库断言
        :param expected_results: 预期结果,yaml文件的SQL语句
        :return: 返回flag标识,0表示正常,非0表示测试不通过
        """
        flag = 0
        conn = ConnectMysql()
        db_value = conn.query_all(expected_results)
        if db_value is not None:
            logs.info("数据库断言成功")
        else:
            flag += 1
            logs.error("数据库断言失败,请检查数据库是否存在该数据!")
        return flag

    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

四、项目核心优势 

  1. 高复用性 (High Reusability)

    • 采用分层架构设计,基础类与工具类(Request封装、数据库操作、加密工具)高度封装。

    • 新增接口用例仅需在 YAML 中维护数据,实现了**“零代码”**编写测试用例,显著降低了脚本维护成本。

  2. 强适应性 (High Adaptability)

    • 深度契合物流业务场景,覆盖订单创建、轨迹查询、仓储管理等多模块接口。

    • 底层支持 GET/POST/PUT/DELETE 全方法,完美兼容 JSON、Form-Data、File Upload 等多种数据传输格式。

  3. 可视化报告 (Visualized Reporting)

    • 集成 Allure 测试报告框架,提供直观的 Pass/Fail 统计图表。

    • 报告详情包含完整的接口请求头、响应体及断言失败堆栈信息,极大缩短了 Bug 定位与回归测试的时间。

  4. CI/CD 友好型设计 (CI/CD Readiness) 

    • 框架采用 CLI (命令行) 驱动 模式,解耦了对 IDE 的依赖。

    • 通过 requirements.txt 管理依赖,且支持无头模式运行,天然具备接入 Jenkins、GitLab CI 等持续集成流水线的能力,为未来实现自动化回归与每日构建打下基础。

  5. 灵活扩展 (High Extensibility)

    • 预留了丰富的 Hook 扩展点,支持后续接入 UI 自动化驱动或性能测试模块。

    • 支持多数据源扩展(如由 SQLite 扩展至 MySQL/PostgreSQL),支持自定义加密算法(MD5/AES/RSA)的动态注入,适应业务迭代需求。


五、环境搭建

1. 环境准备

  • 基础环境:Python 3.8+,JDK 1.8+ (Allure 运行依赖 Java 环境)。

  • 依赖安装

    Bash
    pip install -r requirements.txt
    

    (包含 pytest, allure-pytest, requests, pyyaml, faker 等核心库)

  • 工具配置

    • 下载并解压 Allure Commandline 工具。

    • 关键步骤:将 Allure 的 bin 目录添加到系统的 Path 环境变量中,并验证 allure --version 是否成功。

六、项目结语

    本项目立足于复杂的物流业务场景,从 0 到 1 构建了一套基于 “关键字驱动 + 数据驱动” 的接口自动化测试框架。

    通过对 Requests 的二次封装和 Pytest 钩子函数的深度定制,我们解决了传统脚本维护难、接口依赖处理繁琐、报告不直观等痛点。该框架不仅实现了核心业务链路(如:登录->下单->支付->履约)的自动化闭环,保障了系统的稳定性;其模块化的架构设计也为后续接入 Jenkins 持续集成体系 预留了标准接口。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐