# api_auto_frame_public **Repository Path**: la1nce4/api_auto_frame_public ## Basic Information - **Project Name**: api_auto_frame_public - **Description**: 接口自动化框架(脱敏对外版) - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-04-11 - **Last Updated**: 2025-04-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # api_auto_frame ## 框架介绍 ​ 本框架主要是基于 python + pytest + allure + log + yaml + mysql + 钉钉/邮箱通知 实现的接口自动化框架 ​ 使用 python 语言编写,结合 pytest 进行二次开发,用户仅需要在 yaml 文件中编写测试用例,编写成功之后,会自动生成 pytest 的代码,零基础代码小白,也可以操作 ## 实现功能 - 测试数据隔离, 实现数据驱动 - 用例依赖 - 支持多接口数据依赖:如A接口需要同时依赖B、C接口的响应数据作为参数 - 支持sql执行数据依赖:如接口需要可以在数据库插入数据 - 断言 - 动态多断言: 如接口需要同事校验响应数据和sql校验,支持多场景断言 - 数据库断言:直接在测试用例中写入查询的sql即可断言,无需编写代码 - 自动生成用例代码:测试人员在yaml文件中填写好测试用例,程序可以直接生成用例代码,小白也能使用 - 用例运行时长监控:拓展功能,订制开关,可以决定是否需要使用 - 执行官通知:执行成功之后,可选择发送钉钉、邮箱通知 - 内置拓展函数:如用例中需要生成的随机数据,可直接调用 ## 目录结构 ``` ├── business // 业务代码 │ └── login // 登录程序 │ └── openapiImpl // 开放平台调用程序 ├── common // 配置 │ └── config.yaml // 公共配置 │ └── database.yaml // 数据库连接参数配置 ├── datas // 数据 └── cases // 测试用例yaml数据 └── download // 下载的文件 └── upload // 上传的文件 ├── interface // 接口程序 ├── logs // 日志存放 ├── plugins // 第三方插件 ├── reports // 测试报告层 ├── test_case // 测试用例代码程序 ├── utils // 工具类 │ └── allure_tools // allure工具模块 │ └── allure_data.py // allure数据处理封装 │ └── allure_report_data.py // allure报告数据处理封装 │ └── assertion // 断言 │ └── assert_control.py // 断言控制 │ └── assert_type.py // 断言类型 │ └── case_handler // 用例处理 │ └── case_code_automatic_control.py // 测试用例代码文件生成 │ └── case_code_template.py // 测试代码模板 │ └── case_data_control.py // 测试用例数据处理 │ └── data_handler // 数据处理 │ └── cache_control.py // 系统缓存控制 │ └── case_cache_handler.py // 用例缓存处理 │ └── data_encrypt_decrypt.py // 数据加解密 │ └── data_models.py // 数据类型校验 │ └── enums.py // 枚举层 │ └── sub_control.py // 数据内容替换 │ └── database_tools // 数据库工具 │ └── database_link_control.py // 数据库连接控制 │ └── database_tool_utils.py // 数据库常用工具封装 │ └── mongodb_control.py // mongodb数据库封装 │ └── mysql_control.py // mysql数据库封装 │ └── redis_control.py // redis数据库封装 │ └── files_tools // 文件处理工具 │ └── execl_control.py // ececl文件操作功能封装 │ └── file_handle.py // 文件处理封装 │ └── ini_control.py // ini文件读写控制封装 │ └── json_control.py // json文件读写控制封装 │ └── yaml_control.py // yaml文件读写控制封装 │ └── logging // 日志工具 │ └── log_control.py // 日志控制 │ └── log_decorator.py // 日志装饰器 │ └── notify_tools // 通知工具模块 │ └── ding_talk.py // 钉钉通知 │ └── send_email.py // 邮件通知 │ └── other_utils // 其他封装 │ └── exceptions.py // 自定义错误类型 │ └── time_handler.py // 时间处理封装 │ └── utils.py // 未分类封装 │ └── project // 项目相关 │ └── config_data_handler.py // 公共配置数据控制 │ └── system_control.py // 系统控制 │ └── report_handler // 报告数据处理 │ └── error_case_to_excel.py // 错误用例导入execl │ └── notification_message.py // 测试报告消息推送 │ └── request_tools // 请求工具 │ └── request_control.py // 请求二次封装 │ └── request_setup.py // 请求前置处理 │ └── request_teardown.py // 请求后置处理 │ └── thread // 多线程相关 │ └── thread_data_control.py // 多线程控制 ├── conftest.py // pytest配置文件 ├── pytest.ini // pytest配置文件 ├── Readme.md // help ├── run.py // 运行入口 ``` ## 使用教程 ### 准备工作 #### 依赖库 ``` ``` #### 环境搭建 首先,执行本框架之后,需要搭建好 python、jdk、 allure环境 搭建python教程:[http://c.biancheng.net/view/4161.html](http://c.biancheng.net/view/4161.html) 搭建jdk环境:[https://www.cnblogs.com/zll-wyf/p/15095664.html](https://www.cnblogs.com/zll-wyf/p/15095664.html) 安装allure:[https://blog.csdn.net/m0_49225959/article/details/117194318](https://blog.csdn.net/m0_49225959/article/details/117194318) 如上环境如都搭建好,则安装本框架的所有第三方库依赖,执行如下命令 pip3 install -r requirements.txt ![img.png](E:/DATA/PycharmProjects/pytest-auto-api2/Files/image/安装异常.png) 如果在安装过程中出现如下 Could not find a version 类似的异常, 不用担心,可能是因为你安装的python环境 版本和我不一致导致的,直接 pip install 库名称,不指定版本安装就可以了。 如上方截图说没有找到 asgiref==3.5.1,报错的意思是,没有找到3.5.1这个版本,那么直接控制台输入 pip3 install asgiref 进行安装即可 ### 用例文件创建参数说明 下方内容是一个用例中需要维护的相关字段,下面我会对每个字段的作用,做出解释。 ``` # @File : test_case.yaml # @remark : 测试用例文件示例 # @Author : 潘裕荣 # 公共参数 case_common: allureEpic: '业务系统' allureFeature: '乘客中心' allureStory: '乘客限制名单' # 测试用例 search_01: # host:直接填或写获取域名的函数名及其实参从程序中获取域名 host: '$func{{get_host(oa)}}' path: '/api/oaUserCenterManager/userCenterCtr/BalanceUseLimit/select' # method:请求方式,支持GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS method: 'POST' detail: '查询接口01' headers: Content-Type: 'application/json; charset=UTF-8' # cookie:直接填cookie或写存入缓存的cookie名称从缓存中获取token cookie: '$cache{{loginOaCookie}}' # requestPayloadType:请求参数的数据类型 NONE、PARAMS、DATA、JSON、UPLOAD、DOWNLOAD requestPayloadType: 'JSON' requestPayload: data: '{"pageSize":20,"currentPage":1,"body":{"uid":"1011009312036","phone":null,"startTime":null,"endTime":null,"useType":null}}' menuId: 30666 menuName: '/configCenter/limitRoster' op: 'selectList' systemName: '\u4E58\u5BA2\u753B\u50CF\u7CFB\u7EDF' # token:直接填token或写存入缓存的token名称从缓存中获取token token: '$cache{{loginOaToken}}' # setup:前置处理,依赖其他用例的结果 或 数据库执行sql。运行节点为用例请求执行前。 setup: # aimType:前置处理目标源:当为sql时执行sql类型,否则执行caseId请求,此时该caseId的isRun、assertData、currentCaseDeleteCache不生效。(注:不得调用当前用例ID的数据,避免出现请求无限嵌套调用自身请求现象,程序无法终止) - aimType: 'sql' sqlData: - databaseType: 'MYSQL' databaseName: 'usercenter_01' sql: 'select count(uid) as number from usercenter.balance_use_limit_roster where uid = 1011009312036' setCache: # valueKey:设置系统缓存值在sql中的字段名称,非查询类sql时的名称为 'rowcount' - valueKey: 'number' name: 'search_01_setup_mysql_uid' # - databaseType: 'MYSQL' # databaseName: 'authoritycenter' # sql: 'update authoritycenter.department set orgStatus=-2 WHERE code = "1000010001900638"' # setCache: # - valueKey: 'rowcount' # name: 'search_01_setup_mysql_uid' - aimType: 'case' caseData: - caseId: 'search_03' # 在当前该被调用用例请求前,替换原有数据 subCaseData: # subKey:语法规则为python读取yaml数据的语法 - subKey: 'search_03["requestPayload"]["test"]' subValue: '替换数据,做到同一用例在被不同用例调用时,请求参数可以自定义调整' # 依赖被调用用例的参数 dependentCaseData: - dependentType: 'request' # jsonpath:通过jsonpath提取实际结果,例:$.data>$.body.uid 、 $.code # 注:1、'>' 表示连续使用jsonpath对上一提取结果进行再次提取 2、当列表最终结果只有一个时,会去掉列表 3、当结果为 False 时,会将False转化为空列表[] jsonpath: '$.data>$.body.uid' setCacheName: 'search_01_setup_search_03_request_uid' - dependentType: 'response' jsonpath: '$.data>$[*].uid2' setCacheName: 'search_01_setup_search_03_response_uid' # # 调用自身会报错抛出异常 # - caseId: 'search_01' # currentRequestSetCache:将当前用例的请求结果设置为缓存,运行节点为用例请求执行后 currentRequestSetCache: - type: 'request' jsonpath: '$.data>$.body.uid' name: 'search_01_set_cache_request_uid' - type: 'response' jsonpath: '$.data>$[*].uid' name: 'search_01_set_cache_response_uid' # sleep:用例执行中断(秒)。运行节点为用例请求执行后teardown执行前 sleep: 0 # teardown:后置处理,依赖其他用例的结果 或 数据库执行sql。运行节点为用例请求执行后。参考setup teardown: - aimType: 'sql' sqlData: - databaseType: 'MYSQL' databaseName: 'usercenter_01' sql: 'select count(uid) as uidCount from usercenter.balance_use_limit_roster where uid in $cache{{search_01_set_cache_response_uid}}' setCache: - valueKey: 'uidCount' name: 'search_01_teardown_mysql_uid' - aimType: 'case' caseData: - caseId: 'search_03' dependentCaseData: - dependentType: 'request' jsonpath: '$.data>$.body.uid' setCacheName: 'search_01_teardown_search_03_request_uid' # assertData: # 1、当 assertType: 'STATUS_CODE'时,jsonpath不需要提供 # 2、当为数据库类型断言,需提供 'sqlData' 属性,sql必须为查询类 # 3、当为数据库类型断言,value 为sql语句中查询字段的名称 assertData: - message: "状态码为 200" assertType: 'STATUS_CODE' expect: - value: 200 compareType: '==' - value: 200 compareType: '==' - message: "断言为 1" assertType: 'RES_TEXT' expect: - value: 1 compareType: '==' jsonpath: '$.code' - value: 2 compareType: 'ne' jsonpath: '$.code' - message: '数据库断言测试' assertType: 'RES_SQL' sqlData: databaseType: 'MYSQL' databaseName: 'usercenter_01' # sql:当使用数据替换时,会转化为mysql元组 sql: 'select count(uid) as number from usercenter.balance_use_limit_roster where uid in $cache{{search_01_set_cache_response_uid}}' expect: - value: 'number' compareType: 'len_eq' jsonpath: '$.data>$[*].uid' # currentCaseDeleteCache:删除系统缓存。运行节点为在当前用例断言结束之后 currentCaseDeleteCache: - delType: 'eq' delData: - 'search_01_teardown_search_03_response_uid' - 'search_01_teardown_search_03_response_uid' - delType: 're' delData: - '^search_01_setup' - 'request_uid$' # 是否执行,空或者 True 都会执行 isRun: True ``` #### 1、case_common 1、释义:当前用例文件公共参数 2、是否必填:必填 3、value值格式:字典 ##### 1.1)allureEpic 1、释义:allure报告中功能的一级菜单名称 2、是否必填:必填 3、value值格式:字符串 ##### 1.2)allureFeature 1、释义:allure报告中功能的二级菜单名称 2、是否必填:必填 3、value值格式:字符串 ##### 1.3)allureStory 1、释义:allure报告中功能的三级菜单名称 2、是否必填:必填 3、value值格式:字符串 #### 2、search_01 1、释义:测试用例ID 2、是否必填:必填 3、注意事项: 3.1)测试用例ID必须唯一 3.2)测试用例ID不得与系统内置关键字同名(程序会将每一条测试用例数据存入系统缓存中,后文解释该内容) 4、value值格式:字典 ##### 2.1)host 1、释义:请求接口的域名 2、是否必填:必填 3、value值格式:字符串 ##### 2.2)path 1、释义:请求接口的路径 2、是否必填:必填 3、value值格式:字符串 ##### 2.3)method 1、释义:请求方式 2、是否必填:必填 3、value值格式:字符串 4、value值范围:GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS ##### 2.4)detail 1、释义:用例详情 2、是否必填:必填 3、value值格式:字符串 ##### 2.5)headers 1、释义:请求头 2、是否必填:必填 3、value值格式:字典 ##### 2.6)requestPayloadType 1、释义:请求类型 2、是否必填:必填 3、value值格式:字符串 4、value值范围:NONE、PARAMS、DATA、JSON、UPLOAD、DOWNLOAD ##### 2.7)requestPayload 1、释义:请求参数 2、是否必填:非必填 3、value值格式:任意(注:当requestPayloadType为UPLOAD时,有特殊参数格式要求,详情见下文) 4、value值范围: ##### 2.8)setup ![test_case_file_setup](datas\images\test_case_file_setup.png) 1、释义:前置处理,依赖其他用例的结果 或 数据库执行sql 2、是否必填:非必填 3、value值格式:字典列表 4、value值范围: 4.1)aimType 4.1.1)释义:前置处理目标源 4.1.2)是否必填:必填 4.1.3)value值格式:字符串 4.1.4)value值范围:sql、测试用例ID 4.2)sqlData 4.2.1)释义:执行sql数据 4.2.2)是否必填:当aimType为sql时必填 4.2.3)value值格式:字典列表 4.2.4)value值范围: 4.2.4.1)databaseType 4.2.4.1.1)释义:数据库类型 4.2.4.1.2)是否必填:必填 4.2.4.1.3)value值格式:字符串 4.2.4.1.4)value值范围:mysql 4.2.4.2)databaseName 4.2.4.2.1)释义:数据库名称 4.2.4.2.2)是否必填:必填 4.2.4.2.3)value值格式:字符串 4.2.4.2.4)value值范围:已配置的数据库 4.2.4.2)databaseName 4.2.4.2.1)释义:数据库名称 4.2.4.2.2)是否必填:必填 4.2.4.2.3)value值格式:字符串 4.2.4.2.4)value值范围:已配置的数据库 4.2.4.3)sql 4.2.4.3.1)释义:执行语句 4.2.4.3.2)是否必填:必填 4.2.4.3.3)value值格式:字符串 4.2.4.3.4)注意事项:查询sql类的结果最多只能一条 4.2.4.4)setCache 4.2.4.4.1)释义:设置系统缓存 4.2.4.4.2)是否必填:非必填 4.2.4.4.3)value值格式:字典列表 4.2.4.4.4)value值范围: 4.2.4.4.4.1)valueKey: 4.2.4.4.4.1.1)释义:设置系统缓存值在sql中的字段名称 4.2.4.4.4.1.2)是否必填:必填 4.2.4.4.4.1.3)value值格式:字符串 4.2.4.4.4.2)name: 4.2.4.4.4.2.1)释义:系统缓存名称 4.2.4.4.4.2.2)是否必填:必填 4.2.4.4.4.2.3)value值格式:字符串 4.2.4.4.4)注意事项: 4.3)caseData 4.3.1)释义:执行case数据 4.3.2)是否必填:当aimType为case时必填 4.3.3)value值格式:字典列表 4.3.4)value值范围: 4.3.4.1)caseId 4.3.4.1.1)释义:测试用例ID 4.3.4.1.2)是否必填:必填 4.3.4.1.3)value值格式:字符串 4.3.4.1.4)注意事项:得调用当前用例ID的数据,避免出现请求无限嵌套调用自身请求现象,程序无法终止 4.3.4.2)dependentCaseData 4.3.4.2.1)释义:依赖的测试用例数据 4.3.4.2.2)是否必填:非必填 4.3.4.2.3)value值格式:字典列表 4.3.4.2.4)value值范围: 4.3.4.2.4.1)dependentType 4.3.4.2.4.1.1)释义:依赖的测试用例数据类型 4.3.4.2.4.1.2)是否必填:必填 4.3.4.2.4.1.2)value值格式:字符串 4.3.4.2.4.1.2)value值范围:request、response 4.3.4.2.4.2)jsonpath 4.3.4.2.4.2.1)释义:jsonpath提取语法 4.3.4.2.4.2.2)是否必填:必填 4.3.4.2.4.2.2)value值格式:字符串 4.3.4.2.4.3)setCacheName 4.3.4.2.4.3.1)释义:设置系统缓存名称 4.3.4.2.4.3.2)是否必填:必填 4.3.4.2.4.3.2)value值格式:字符串 4.3.4.3)subCaseData 4.3.4.3.1)释义:在当前该被调用用例请求前,替换原有数据 4.3.4.3.2)是否必填:非必填 4.3.4.3.3)value值格式:字典列表 4.3.4.3.4)value值范围: 4.3.4.3.4.1)subKey 4.3.4.3.4.1.1)释义:被替换数据的key,语法规则为python读取yaml数据的语法 4.3.4.3.4.1.2)是否必填:必填 4.3.4.3.4.1.2)value值格式:字符串 4.3.4.3.4.1)subValue 4.3.4.3.4.1.1)释义:替换后的数据 4.3.4.3.4.1.2)是否必填:非必填 4.3.4.3.4.1.2)value值格式:任意 5、注意事项: 5.1)setup运行节点为当前用例请求之前 5.2)此时被调用的该caseId的isRun、assertData、currentCaseDeleteCache不生效 ##### 2.9)currentRequestSetCache ![test_case_file_currentRequestSetCache](datas\images\test_case_file_currentRequestSetCache.png) 1、释义:将当前用例的请求结果设置为缓存 2、是否必填:非必填 3、value值格式:字典列表 4.1)type 4.1.1)释义:数据类型 4.1.2)是否必填:必填 4.1.3)value值格式:字符串 4.1.4)value值范围:request、response 4.2)jsonpath 4.2.1)释义:jsonpath提取语法 4.2.2)是否必填:必填 4.2.3)value值格式:字符串 4.2)name 4.3.1)释义:设置的系统缓存名称 4.3.2)是否必填:必填 4.3.3)value值格式:字符串 5、注意事项:运行节点为用例请求执行后 ##### 2.10)sleep 1、释义:用例执行中断(秒) 2、是否必填:非必填 3、value值格式:整数、浮点数 4、注意事项:运行节点为用例请求执行后 ##### 2.10)teardown 1、释义:后置处理,依赖其他用例的结果 或 数据库执行sql 2、是否必填:非必填 3、value值格式:字典列表 4、value值范围:参考setup 5、注意事项:运行节点为用例请求执行后,断言之前 ##### 2.10)assertData ![test_case_file_assertData](datas\images\断言字段格式.png) 1、释义:断言 2、是否必填:非必填 3、value值格式:字典列表 4、value值范围: 4.1)message 4.1.1)释义:断言描述 4.1.2)是否必填:非必填 4.1.3)value值格式:字符串 4.2)assertType 4.2.1)释义:断言类型 4.2.2)是否必填:必填 4.2.3)value值格式:字符串 4.2.4)value值范围:STATUS_CODE、RES_TEXT、RES_SQL、REQ_SQL 4.3)sqlData 4.3.1)释义:sql执行信息 4.3.2)是否必填:当 assertType 为 RES_SQL、REQ_SQL 数据库断言类型时必填 4.3.3)value值格式:字典列表 4.3.4)value值范围: 4.3.4.1)databaseType 4.3.4.1.1)释义:数据库类型 4.3.4.1.2)是否必填:必填 4.3.4.1.3)value值格式:字符串 4.3.4.1.4)value值范围:mysql、mongodb 4.3.4.2)databaseName 4.3.4.2.1)释义:数据库名称 4.3.4.2.2)是否必填:必填 4.3.4.2.3)value值格式:字符串 4.3.4.2.4)value值范围:程序中已配置的数据库 4.3.4.3)sql 4.3.4.3.1)释义:sql语句 4.3.4.3.2)是否必填:必填 4.3.4.3.3)value值格式:当databaseType为mysql时字符串,当databaseType为mongodb时列表(aggregate聚合查询语句内容) 4.3.4.3.4)value值范围:查询类的sql语句 4.4)expect 4.4.1)释义:期望结果 4.4.2)是否必填:必填 4.4.3)value值格式:字典列表 4.3.4)value值范围: 4.3.4.1)jsonpath 4.3.4.1.1)释义:jsonpath提取语法,根据assertType类型提取请求参数RES、响应结果REQ的数据 4.3.4.1.2)是否必填:当 assertType 为 RES_TEXT、RES_SQL、REQ_SQL 断言类型时必填 4.3.4.1.3)value值格式:字符串 4.3.4.2)compareType 4.3.4.2.1)释义:比较规则 4.3.4.2.2)是否必填:必填 4.3.4.2.3)value值格式:字符串 4.3.4.2.4)value值范围:以下为部分类型,以程序中为准 ![test_case_file_assertData_compareType](datas\images\断言比较类型.png) 4.3.4.2)value 4.3.4.2.1)释义:期望值 4.3.4.2.2)是否必填:必填 4.3.4.2.3)value值格式:任意 4.3.4.2.4)value值范围:当 assertType 为 RES_SQL、REQ_SQL 数据库断言类型时,value为sql执行结果返回字段的名称 5、注意事项:运行节点为teardown之后 ##### 2.11)currentCaseDeleteCache 1、释义:删除系统缓存 2、是否必填:非必填 3、value值格式:字典列表 4、value值范围: 4.1)delType 4.1.1)释义:删除类型 4.1.2)是否必填:必填 4.1.3)value值格式:字符串 4.1.4)value值范围:eq、re 4.2)delData 4.2.1)释义:匹配的删除数据 4.2.2)是否必填:必填 4.2.3)value值格式:字符串列表 5、注意事项:运行节点为在当前用例断言结束之后 ##### 2.12)isRun 1、释义:是否执行用例 2、是否必填:非必填,默认执行 3、value值格式:布尔、字符串 ### 用例文件的存放 用例文件需要存放在指定位置下 config.yanl文件下可以自定义路径: ![image-20230711084848186](datas\images\用例文件存放位置配置.png) 当前配置的存放位置: ![image-20230711085109392](datas\images\用例文件存放位置.png) ### 测试代码文件自动生成 在生成测试代码之前需要配置相关参数: ![image-20230711085510058](datas\images\测试代码文件配置.png) 生成测试代码: ![image-20230711090512207](datas\images\生成测试代码程序.png) 1、在用例文件存放文件夹下方创建相关的yaml用例 2、写完之后,可以执行 utils\files_tools\case_code_file_automatic_control.py 这个文件的程序,自动生成测试代码文件 3、执行case_code_file_automatic_control.py文件之后,会发现,在test_case层新增该条用例的对应代码,可直接执行该用例调试 4、注意!!!如果生成对应的测试代码之后,期间有更改过yaml用例中的内容,需要重新生成代码 ### 用例的执行 所有接口都编写好之后,可以直接运行run.py主程序,会根据配置执行自动化接口 ### 系统拓展功能 #### 1、jsonpath提取规则二次封装 1.1)jsonpath提取语法 ``` jsonpath提取二次封装,可连续提取对象中的参数,解决value值为字符串的问题。例: 提取内容:{'data': '{"pageSize":20,"currentPage":1,"body":{"uid":"1011009312036","phone":null,"startTime":null,"endTime":null,"useType":null}}'} 提取语法:$.data>$.body.uid 注:'>' 表示连续使用jsonpath对上一提取结果进行再次提取 ``` 1.1)jsonpath提取返回值特殊处理 ``` 1、当列表最终结果只有一个时,会去掉列表 2、当结果为 False 时,会将False转化为None ``` #### 2、系统缓存添加与调用 2.1)设置系统缓存 在用例文件可以通过setup、teardown、currentRequestSetCache等,在用例执行过程中,将提取到的数据添加到系统缓存内,如果已有同名缓存,重新设置时更新覆盖 例如: ![image-20230627141915352](datas\images\系统缓存_添加.png) 2.2)获取系统缓存 2.2.1)如果已经有添加到系统缓存的数据,可以通过规定的字符串声明替换在你所需要的位置,替换声明:$cache{{缓存名称}} 例如: ![image-20230627142629367](datas\images\系统缓存_获取.png) 2.2.2)用例执行中的替换后效果: ![image-20230627142757402](datas\images\系统缓存_获取2.png) 2.4)可以进行系统缓存替换的用例文件字段 ``` 1、host 2、path 3、detail 4、headers 5、requestPayloadType 6、requestPayload 7、setup - sql 8、sleep 9、teardown- sql 10、assertData - expect - value 11、isRun ``` 2.5)注意事项: 2.5.1)在获取系统缓存内容前必须先添加同名系统缓存进系统中,否则找不到数据 2.5.2)如果系统缓存为 'None' 值,会进行特殊处理,替换为 'null' 2.5.3)在sql语句中进行数据替换时,会将结果转化为mysql语句的元组格式,例如: ``` 1、获取结果:[] 替换后:select count(uid) from usercenter.balance_use_limit_roster where uid in ('') 2、获取结果:1011009312036 替换后:select count(uid) from usercenter.balance_use_limit_roster where uid in (1011009312036) 3、获取结果:[1011009312036, 1011009312036, 1011009312036] 替换后:select count(uid) from usercenter.balance_use_limit_roster where uid in (1011009312036, 1011009312036, 1011009312036) ``` #### 3、自定义拓展方法调用 比如我们有些特殊的场景,可能会涉及到一些定制化的数据,每次执行数据,需要按照指定规则随机生成 3.1)系统内置方法 ![image-20230627164717192](datas\images\函数调用_可调用方法.png) 当前可调用方法: ``` 1、随机数:random_int() 2、随机生成手机号码:get_phone() 3、随机生成身份证号码:get_id_number() 4、女生姓名:get_female_name() 5、男生姓名:get_male_name() 6、生成邮箱:get_email() 7、当前时间:get_now_time() 8、今日0点整时间:get_today_date() 9、一周后12点整的时间:get_time_after_week() 10、获取域名:host(hostName) ``` 3.2)调用方式 3.2.1)可以通过规定的字符串声明替换在你所需要的位置,替换声明:$func{{方法及参数}} 自动化函数传递参数。首先同样和上方一样,创建一个随机生成的方法,该方法支持接收参数 @classmethod def random_int(cls, min_num, max_num) -> int: """ 随机生成指定范围的随机数 :param min_num: :param max_num: :return: """ _data = random.randint(min_num, max_num) return _data 在用例中,假设我们需要获取一个 1-10之间的随机数,那么我们直接这样调用该数据即可 reason: $func{{random_int(1, 10)}} 例如: ![image-20230627165740200](E:\DATA\PycharmProjects\frame\api_auto_frame\datas\images\函数调用_获取.png) 3.2.2)用例执行中的替换后效果: ![image-20230627170601231](datas\images\函数调用_获取2.png) 3.4)可以进行系统缓存替换的用例文件字段 ``` 1、host 2、path 3、detail 4、headers 5、requestPayloadType 6、requestPayload 7、setup - sql 8、sleep 9、teardown- sql 10、assertData - expect - value 11、isRun ``` 3.5)注意事项: sql类型进行数据替换时,会将结果转化为mysql语句的元组格式,例如: ``` 1、获取结果:[] 替换后:select count(uid) from usercenter.balance_use_limit_roster where uid in ('') 2、获取结果:1011009312036 替换后:select count(uid) from usercenter.balance_use_limit_roster where uid in (1011009312036) 3、获取结果:[1011009312036, 1011009312036, 1011009312036] 替换后:select count(uid) from usercenter.balance_use_limit_roster where uid in (1011009312036, 1011009312036, 1011009312036) ``` #### 4、用例运行时长监控 ![image-20230627175229160](datas\images\用例运行时长监控.png) 4.1)runWarningTime为超过给定时间(秒)时,在日志中输出警告信息 4.2)如若不需要监控,可修改switch为False关闭 ### 系统参数配置 #### 1、公共配置 ![image-20230627172845781](datas\images\公共配置.png) 1.1)env:环境参数 1.2)host:域名参数 结合上文的可调用方法使用,即:$func{{get_host(oa)}}。可以按需要新增 1.3)account:账号密码 登录oa系统的账号密码等 1.4)钉钉消息Token、Secret 推送钉钉消息后接收方的配置信息 #### 2、数据库配置 2.1)根据测试环境配置数据库连接信息 ![image-20230627175822509](datas\images\测试环境.png) ![image-20230627175948064](datas\images\数据库配置.png) 2.2)配置你所需要的数据库链接 ![image-20230627180117192](datas\images\数据库配置方法.png) ### 其他程序问题 #### 1、如何发送请求 上方了解了用例的数据结构之后,下面我们开始编写第一个get请求方式的接口 我们来编写测试用例,在 datas-cases 文件夹下面,创建一个名称为collect_tool_list.yaml 的用例文件,请求/lg/collect/usertools/json这个收藏网址列表接口 # 公共参数 case_common: allureEpic: 开发平台接口 allureFeature: 收藏模块 allureStory: 收藏网址列表接口 collect_tool_list_01: host: https://www.apitest123456.com/ path: /lg/collect/usertools/json method: GET detail: 查看收藏网址列表接口 headers: Content-Type: multipart/form-data; cookie: login_cookie # 请求参数的数据类型 NONE、PARAMS、DATA、JSON、FILE、EXPORT requestPayloadType: params requestPayload: pageNum: 1 pageSize: 10 assertData: - message: "断言接口状态码" assertType: 'RES_TEXT' expect: - value: 0 compareType: '==' jsonpath: $.errorCode # 是否执行,空或者 True 都会执行 isRun: 也可以将请求参数拼接中url中,如: # 公共参数 case_common: allureEpic: 开发平台接口 allureFeature: 收藏模块 allureStory: 收藏网址列表接口 collect_tool_list_01: host: https://www.apitest123456.com/ path: /lg/collect/usertools/json?pageNum=1&pageSize=10 method: GET detail: 查看收藏网址列表接口 headers: Content-Type: multipart/form-data; cookie: login_cookie # 请求参数的数据类型 NONE、PARAMS、DATA、JSON、FILE、EXPORT requestPayloadType: params requestPayload: NONE assertData: - message: "断言接口状态码" assertType: 'RES_TEXT' expect: - value: 0 compareType: '==' jsonpath: $.errorCode # 是否执行,空或者 True 都会执行 isRun: #### 2、用例中需要依赖登录的token、cookie,如何在测试前获取并提供给所有测试用例 ![image-20230627174125913](datas\images\OA系统登录Token、Cookie.png) 首先,为了防止重复请求调用登录接口,pytest中的 conftest.py 提供了热加载机制,看上方截图中的代码,我们需要在 conftest.py 提前编写好登录的代码 1.1)获取token:如上方代码所示,在执行用例之前,我们会先去执行获取到响应中的token,然后编写代码通过SystemCacheHandler.set_cache(cacheName='loginOaToken', value=oaToken),将token写入缓存中,其中 loginOaToken 是缓存名称。 1.2)调用token:编写好之后,我们在用例文件中,如果该条用例需要依赖token,则直接进行内容替换,读取缓存中的token 1.3)调用示例 ![image-20230627174805436](datas\images\系统缓存_OA系统token、cookie.png) 1.4)注意事项:由于内置了登录Token、Cookie的系统缓存,所以在编写用例文件时,不要用与loginOaCookie、loginOaToken相同的缓存名称,避免被覆盖修改 1.5)如果不需要获取Token、Cookie,可以将autouse置为False #### 3、如何进行测试断言 ``` assertData: - message: "状态码为 200" assertType: 'STATUS_CODE' expect: - value: 200 compareType: '==' - value: 200 compareType: '==' - message: "断言为 1" assertType: 'RES_TEXT' expect: - value: 1 compareType: '==' jsonpath: '$.code' - value: 2 compareType: 'ne' jsonpath: '$.code' - message: '数据库断言测试' assertType: 'RES_SQL' sqlData: databaseType: 'MYSQL' databaseName: 'usercenter_01' # sql:当使用数据替换时,会转化为mysql元组 sql: 'select count(uid) as number from usercenter.balance_use_limit_roster where uid in $cache{{search_01_set_cache_response_uid}}' expect: - value: 'number' compareType: 'len_eq' jsonpath: '$.data>$[*].uid' ``` - message:断言失败时的说明,非必填 - assertType:断言类型,必填。支持STATUS_CODE、RES_TEXT、RES_SQL、REQ_SQL - sqlData:sql执行信息,当assertType为 RES_SQL、REQ_SQL 时必填 - databaseType:数据库类型mysql、mongodb,必填 - databaseName:数据库名称(以程序中定义的名称为准),必填 - sql:查询类sql执行语句(mysql时为原生执行sql语句,mongodb时为aggregate()执行中的列表数据),必填 - expect: - value:预期结果,必填。当assertType为 STATUS_CODE、RES_TEXT 时,可以填写为任意值。当assertType为 RES_SQL、REQ_SQL 时,为sql中查询字段的名称,断言时会获取对应的值去与实际结果进行比较 - compareType:比较条件,必填。下方截图中,是部分支持的断言比较条件类型说明 ![img.png](E:/DATA/PycharmProjects/pytest-auto-api2/Files/image/assert_type.png) - jsonpath:jsonpath提取规则(二次封装),提取到的数据作为实际结果,非必填 - 当assertType为 STATUS_CODE 时:不需要填 - 当assertType为 RES_TEXT、RES_SQL、REQ_SQL 时:根据当前类型提取请求参数REQ、响应结果RES中的数据 #### 4、断言http响应状态码 相信有些小伙伴在做接口测试的过程中,有部分接口是没有任何响应的,那么在没有响应数据的情况下 我们就只能通过 http的状态码去判断这条用例是否通过,我们可以这样写 assertData: - message: "状态码为 200" assertType: 'STATUS_CODE' expect: - value: 200 compareType: '==' 我们直接在assert下方添加一个 status_code 参数,状态码我们判断其为 200 #### 5、用例出现接口依赖,如何编写测试用例 多业务逻辑这一块,我们拿个简单的例子举例,比如登录场景,在登陆之前,我们需要先获取到验证码 ![img.png](datas\images\接口依赖send_sms_code.png) ![img.png](datas\images\接口依赖login.png) 首先,我们先创建一个 get_send_sms_code.yaml 的文件,编写一条发送验证码的用例 # 公共参数 case_common: allureEpic: oa系统 allureFeature: 登录模块 allureStory: 获取登录验证码 send_sms_code_01: host: '$func{{get_host(oa)}}' path: '/mobile/sendSmsCode' method: 'POST' detail: '正常获取登录验证码' headers: appId: '23132' masterAppId: 'masterAppId' ontent-Type: 'application/json;charset=UTF-8' requestPayloadType: 'JSON' requestPayload: phoneNumber: '181****5163' assertData: - message: "断言code、success" assertType: 'RES_TEXT' expect: - value: '00000' compareType: '==' jsonpath: '$.code' - value: 'true' compareType: ''=='' jsonpath: '$.success' isRun: False 编写好之后,我们再创建一个 login.yaml 文件,也可以在同一文件内,没有限制 # 公共参数 case_common: allureEpic: oa系统 allureFeature: 登录模块 allureStory: 登录 login: # host:直接填或写获取域名的函数名及其实参从程序中获取域名 host: '$func{{get_host(oa)}}' path: '/login/phone' # method:请求方式,支持GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS method: 'POST' detail: '输入验证码进行登录' headers: appId: '23132' masterAppId: 'masterAppId' Content-Type: 'application/json;charset=UTF-8' # requestPayloadType:请求参数的数据类型 NONE、PARAMS、DATA、JSON、FILE、EXPORT requestPayloadType: 'JSON' requestPayload: phoneNumber: '181****5163' code: '$cache{{login_sms_code}}' # setup:前置处理,依赖其他用例的结果 或 数据库执行sql。运行节点为用例请求执行前。 setup: # aimType:前置处理目标源:当为sql时执行sql类型,否则执行caseId请求,此时该caseId的isRun、assertData、currentCaseDeleteCache不生效。(注:不得调用当前用例ID的数据,避免出现请求无限嵌套调用自身请求现象,程序无法终止) - aimType: 'case' caseData: - caseId: 'send_sms_code_01' dependentCaseData: - dependentType: 'response' # jsonpath:通过jsonpath提取实际结果,例:$.data>$.body.uid 、 $.code # 注:1、'>' 表示连续使用jsonpath对上一提取结果进行再次提取 2、当列表最终结果只有一个时,会去掉列表 3、当结果为 False 时,会将False转化为空列表[] jsonpath: '$.code' setCacheName: 'login_sms_code' assertData: - message: "断言code" assertType: 'RES_TEXT' expect: - value: '00000' compareType: '==' jsonpath: '$.code' # 是否执行,空或者 True 都会执行 isRun: True 其中处理接口依赖的核心区域,主要在这里: setup: # aim:前置处理目标源:当为sql时执行sql类型,否则执行caseId请求,此时该caseId的isRun、assertData、currentCaseDeleteCache不生效。(注:不得调用当前用例ID的数据,避免出现请求无限嵌套调用自身请求现象,程序无法终止) - aimType: 'case' caseData: - caseId: 'get_sms' dependentCaseData: - dependentType: 'response' # jsonpath:通过jsonpath提取实际结果,例:$.data>$.body.uid 、 $.code # 注:1、'>' 表示连续使用jsonpath对上一提取结果进行再次提取 2、当列表最终结果只有一个时,会去掉列表 3、当结果为 False 时,会将False转化为空列表[] jsonpath: '$.code' setCacheName: 'login_sms_code' 首先,我们在登录前需要获取验证码,所以需要使用setup前置处理功能,然后设计相关依赖的数据: - aimType:上方场景中,我们登录需要先获取验证码,依赖的接口返回值,所以为aimType:case - caseData:当aimType为case时,caseData不能为空,其值为列表形式,包含caseId、dependentCaseData字段 - caseId:不能为空,上方场景中,依赖的caseId就是发送短信验证码的 caseId:send_sms_code_01 * dependentCaseData:如果只是依赖其他接口发送请求,dependentCaseData为空。上方场景中,我们依赖的是获取短信验证码接口中的响应内容,因此dependentCaseData不能为空,其值为列表形式,包含dependentType、jsonpath、setCacheName字段 * dependentType:我们依赖的是获取短信验证码接口中的响应内容,因此这次填写的是 response, 同样也支持request方式 * jsonpath: 通过jsonpath 提取方式,提取到短信验证码中的验证码内容(jsonpath规则看”系统拓展功能“介绍中的”jsonpath提取二次封装“用法) * setCacheName:拿到验证码之后,这里我们可以自定义一个缓存名称 如: login_sms_code,程序中会将你所提取到的验证码存入缓存中, 因此我们在这条用例的 requestPayload 中,有个code 的参数,值设置成 $cache{login_sms_code},程序中会将我们 send_sms_code_01中的验证码给提取出来,通过 $cache{login_sms_code} 语法获取到。 * 注意:定义缓存名称,每个公司最好定义一个规范,比如 当前这条 case_id名称 + 缓存自定义名称,如 login_sms_code。缓存数据名称、caseId是唯一的, 这样可以避免不同用例、缓存名称之间重复的问题,导致无法获取到对应的缓存数据 #### 6、用例请求时参数需要从数据库中提取,如何编写测试用例 举个例子,比如登录场景,在登陆时我们的手机号,必须是先注册过的,如果你不想去注册想拿之前已经注册好的,此时你知道数据库存有,想在数据库直接拿一个来用,那么可以按下文这样做 首先,我们先创建一个 get_send_sms_code.yaml 的文件,编写一条发送验证码的用例 ``` # 公共参数 case_common: allureEpic: oa系统 allureFeature: 登录模块 allureStory: 获取登录验证码 send_sms_code_01: host: '$func{{get_host(oa)}}' path: '/mobile/sendSmsCode' method: 'POST' detail: '正常获取登录验证码' headers: appId: '23132' masterAppId: 'masterAppId' ontent-Type: 'application/json;charset=UTF-8' requestPayloadType: 'JSON' requestPayload: phoneNumber: '$cache{{send_sms_code_01_phone}}' setup: # aimType:前置处理目标源:当为sql时执行sql类型,否则执行caseId请求,此时该caseId的isRun、assertData、currentCaseDeleteCache不生效。(注:不得调用当前用例ID的数据,避免出现请求无限嵌套调用自身请求现象,程序无法终止) - aimType: 'sql' sqlData: - databaseType: 'MYSQL' databaseName: 'usercenter_01' sql: 'select phone from usercenter.user limit 1' setCache: # valueKey:设置系统缓存值在sql中的字段名称,非查询类sql时的名称为 'rowcount' - valueKey: 'phone' name: 'send_sms_code_01_phone' assertData: - message: "断言code、success" assertType: 'RES_TEXT' expect: - value: '00000' compareType: '==' jsonpath: '$.code' - value: 'true' compareType: ''=='' jsonpath: '$.success' isRun: False ``` 这里我们借用了上文介绍“接口依赖”的数据改造一番,此次也是需要使用setup前置处理功能,然后设计相关依赖的数据: - aimType:上方场景中,我们登录需要先获取注册过的手机号,依赖的数据库查询,所以为aimType:sql - sqlData:当aimType为sql时,sqlData不能为空,其值为字典列表形式,包含databaseType、databaseName、sql、setCache字段 * databaseType:我们需要执行的是mysql查询,因此这次填写的是 mysql * databaseName:执行mysql语句,需要指定数据库名称,数据库名以系统中配置的为准 * sql:sql执行语句 * setCache:设置缓存列表。 * name:拿到验证码之后,这里我们可以自定义一个缓存名称 如: send_sms_code_01_phone,在requestPayload中通过 $cache{send_sms_code_01_phone} 语法获取到 * valueKey:sql中返回的字段名称作为key,获取该值来设置系统缓存 之后,我们再同步修改 login.yaml 文件,调整的内容为requestPayload中phoneNumber取系统缓存值 ``` # 公共参数 case_common: allureEpic: oa系统 allureFeature: 登录模块 allureStory: 登录 login: host: '$func{{get_host(oa)}}' path: '/login/phone' method: 'POST' detail: '输入验证码进行登录' headers: appId: '23132' masterAppId: 'masterAppId' Content-Type: 'application/json;charset=UTF-8' requestPayloadType: 'JSON' requestPayload: # 修改了这里,原本为phoneNumber: '181****5163',写死了手机号,修改后可以同步使用相同的手机号了 phoneNumber: '$cache{send_sms_code_01_phone}' code: '$cache{{login_sms_code}}' setup: - aimType: 'case' caseData: - caseId: 'send_sms_code_01' dependentCaseData: - dependentType: 'response' jsonpath: '$.code' setCacheName: 'login_sms_code' assertData: - message: "断言code、success" assertType: 'RES_TEXT' expect: - value: '00000' compareType: '==' jsonpath: '$.code' isRun: True ``` #### 7、依赖接口的业务执行需要等待一段时间怎么办 程序中可以设定接口请求之后,等待时长,假设A接口依赖B接口的业务,A接口请求完时,我们需要让他等待几秒钟 再次请求B接口,这样的话,我们可以使用sleep关键字 sleep: 3 #### 8、请求参数为路径参数怎么办 delete_01: host: $func{{get_host(oa)}} path: '/api/oaUserCenterManager/userCenterCtr/BalanceUseLimit/delete/$cache{{delete_01_setup_search_01_response_uid}}' method: 'POST' detail: '删除接口01' headers: Content-Type: 'application/json; charset=UTF-8' cookie: $cache{{loginOaCookie}} requestPayloadType: 'JSON' requestPayload: None setup: - aimType: 'case' caseData: - caseId: 'search_01' dependentCaseData: - dependentType: 'response' jsonpath: '$.data>$[*].uid' setCacheName: 'delete_01_setup_search_01_response_uid' 以上方实例,我们的参数是在url中的,因此我们可以通过 setup 获取到我们需要依赖的数据,将本条用例需要用到的数据存入缓存,从而在 path 直接调用缓存数据即可 #### 9、如何将当前用例的请求参数、响应结果存入缓存中 比如想要做数据库的断言,但是这个字段接口没有返回,我应该怎么去做校验呢? 程序中提供了currentRequestSetCache这个关键字,可以将当前这条用例的请求数据 或者响应数据 给直接存入缓存中 如下案例所示: # currentRequestSetCache:将当前用例的请求结果设置为缓存,运行节点为用例请求执行后 currentRequestSetCache: - type: 'request' jsonpath: '$.data>$.body.uid' name: 'search_01_set_cache_request_uid' - type: 'response' jsonpath: '$.data>$[*].uid' name: 'search_01_set_cache_response_uid' #### 10、用例中如何生成随机数据 比如我们有些特殊的场景,可能会涉及到一些定制化的数据,每次执行数据,需要按照指定规则随机生成 ``` collect_addtool_01: host: https://www.apitest123456.com/ path: /lg/collect/addtool/json method: POST detail: 新增收藏网址接口 headers: cookie: $cache{login_cookie} requestPayloadType: data requestPayload: # 随机生成name name: 自动生成收藏网址${{random_int(1,1000)}} link: https://www.baidu.com/ ``` 那么我们就在 sub_control.py 文件中,编写 get_random_int() 的方法 ``` @classmethod def get_random_int(cls, min_num, max_num) -> int: """ 随机生成指定范围的随机数 :param min_num: :param max_num: :return: """ _data = random.randint(min_num, max_num) return _data ``` 编写好之后,在用例中调用如下 name: 自动生成收藏网址$func{{random_int(1,1000)}} 使用 " $func{{random_int(1,1000)}}" 的方法,程序调用时,会生成随机数。在 rsub_control.py 文件中,我还封装了一些常用的随机数,如随机生成男生姓名、女生姓名、身份证、邮箱、手机号码之类的,方便大家使用,如随机生成邮箱,我们在用例中编写的格式为 " $func{{get_email()}} " 。其他所需随机生成的数据,可在文件中自行添加函数逻辑。如果对此解答还有疑惑的,请查阅上文中的 “系统拓展功能” 介绍的“可调用方法”篇章 #### 11、用例结束时,如何还原测试环境数据 通常情况下,我们做自动化所有新增的数据,我们测试完成之后,都需要讲这些数据删除,程序中提供teardown的功能,支持两种写法 一种是直接调用接口进行数据删除;另外一种是直接删除数据库中的数据。建议使用第一种,直接调用业务接口删除对应的数据 1、下面我们先来看看第一种删除方式,因为需要兼容较多的场景,因此使用功能上相对也会比较复杂 需要小伙伴们一个一个去慢慢的理解。 下面为了方便大家对于teardown功能的理解,我会针对不同的场景进行举例: * 假设现在我们有一个新增接口,写完之后,我们需要先调用查询接口获取到新增接口的ID,然后再调用删除接口进行删除(这里有逻辑上的先后关系,例如查询接口是先发送请求再提取数据) 那么针对这个场景,我们就需要有个关键字去做区分,什么场景下先发送请求,什么场景下后发送请求,下面我们来看一下案例,方便大家理解 collect_addtool_01: host: https://www.apitest123456.com/ path: /lg/collect/addtool/json method: POST detail: 新增收藏网址接口 headers: cookie: $cache{login_cookie} requestPayloadType: data requestPayload: name: 自动生成收藏网址${{random_int()}} link: https://www.baidu.com/ # currentRequestSetCache:将当前用例的请求结果设置为缓存,运行节点为用例请求执行后 currentRequestSetCache: # 第1步:先从当前用例的新增接口请求参数中,获取到随机生成的name - type: 'request' jsonpath: '$.name' name: 'collect_addtool_01_name' teardown: - aimType: 'case' caseData: # 第2步:通过查询品牌审核列表接口,利用name参数查询到ID并存入缓存 - caseId: 'query_apply_list_01' dependentCaseData: # 因为是获取该caseId的响应内容,我们dependentType需要写成 response - dependentType: 'response' jsonpath: '$.data>$.body.id' setCacheName: 'collect_addtool_01_id' # 第3步:通过删除接口,利用ID参数进行删除。删除的话,我们是直接发送请求即可,所以不需要dependentCaseData - caseId: 'delete_01' isRun: True query_apply_list_01: host: https://www.apitest123456.com/ path: /lg/collect/query method: POST detail: 查询收藏网址接口 headers: cookie: $cache{login_cookie} requestPayloadType: data requestPayload: name: $cache{collect_addtool_01_name} isRun: False delete_01: host: https://www.apitest123456.com/ path: /lg/collect/delete method: POST detail: 删除收藏网址接口 headers: cookie: $cache{login_cookie} requestPayloadType: data requestPayload: id: $cache{collect_addtool_01_id} isRun: False - 在teardown调用delete_01时,上文举例delete_01中的requestPayload['id']已经提前声明的要获取缓存再发出请求。假如遇到多个用例均需要调用 delete_01,就显示混乱了,因为requestPayload['id']已经写死参数,只能使用 $cache{collect_addtool_01_id} 缓存,为了解决这个问题,可以使用“请求前替换原有数据”功能: ``` collect_addtool_01: host: https://www.apitest123456.com/ path: /lg/collect/addtool/json method: POST detail: 新增收藏网址接口 headers: cookie: $cache{login_cookie} requestPayloadType: data requestPayload: name: 自动生成收藏网址${{random_int()}} link: https://www.baidu.com/ currentRequestSetCache: - type: 'request' jsonpath: '$.name' name: 'collect_addtool_01_name' teardown: - aimType: 'case' caseData: - caseId: 'query_apply_list_01' dependentCaseData: - dependentType: 'response' jsonpath: '$.data>$.body.id' setCacheName: 'collect_addtool_01_id' - caseId: 'delete_01' # 主要修改了以下内容 # subCaseData可以为空,不为空时则替换被调用用例的数据 subCaseData: # subKey:语法规则为python读取yaml数据的语法 - subKey: 'delete_01["requestPayload"]["id"]' subValue: '$cache{collect_addtool_01_id}' isRun: True query_apply_list_01: host: https://www.apitest123456.com/ path: /lg/collect/query method: POST detail: 查询收藏网址接口 headers: cookie: $cache{login_cookie} requestPayloadType: data requestPayload: name: $cache{collect_addtool_01_name} isRun: False delete_01: host: https://www.apitest123456.com/ path: /lg/collect/delete method: POST detail: 删除收藏网址接口 headers: cookie: $cache{login_cookie} requestPayloadType: data requestPayload: # 主要修改了以下内容,可以为任意内容,不影响collect_addtool_01替换delete_01的数据 id: '123' isRun: False ``` 2、 teardown后置使用sql删除数据 如一些特殊场景,业务上并没有提供删除接口,我们也可以直接通过 sql去讲对应的sql删除 ``` teardown: - aimType: 'sql' sqlData: - databaseType: 'MYSQL' databaseName: 'collect' # 这里的sql没用 “id = $cache{collect_addtool_01_id}” 的原因,请查看“系统拓展功能” 中关于sql时获取系统缓存的介绍 sql: 'delete from collect where id in $cache{collect_addtool_01_id}' ``` ### ## 遇到问题 ``` 1、 问题:allure-pytest=2.13.0与pytest-rerunsfailures=10.3搭配使用时,不明原因导致生成allure报告后会出现多条重复的失败用例记录,且没有标记重跑次数 解决:使用低版本allure-pytest=2.10.0或更低 ``` ``` 2、 问题:pytest.ini 文件加注解报错,UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte 0xaf 解决: 1)选中pytest.ini,点击选中File->File Properties->File Encoding 2)找到GBK格式,选中保存 3)重新执行正常脚本,不再报错 ``` #### 12、如何测试上传文件接口 12.1)首先,我们将所有需要测试的文件,全部都放在 datas\files\upload 文件夹中 ![image-20230710144523975](datas\images\测试上传文件位置.png) 12.2)在yaml文件中,我们需要注意两个地方,主要是用例中的requestPayloadType、和 requestPayload 字段: - requestPayloadType: 上传文件,我们需要更改成 upload - requestPayload:上传文件参数,新增一个字典列表,在下方传我们需要的数据,列表中的每一段数据代表着一个参数所包含的信息 12.3)requestPayload的参数我们结合实际例子来看应当如何编写: **分析接口** ​ 首先浏览器f12查看接口内容(主要看[接口类型](https://so.csdn.net/so/search?q=接口类型&spm=1001.2101.3001.7020)、请求头、Payload)。发现上传文件的接口是post类型,请求头中Content-Type也很重要,指定内容类型及请求体的一个分隔符。详见下图。 ![img](datas\images\上传文件参数01.png) ​ Payload里是接口的[请求体](https://so.csdn.net/so/search?q=请求体&spm=1001.2101.3001.7020),详见下图。接口参数:type、orgType、file ,分别对应下图。其中,file的值为上传的文件(转换为二进制数据)![img](datas\images\上传文件参数02.png) 对应参数的请求内容,其中------WebKitFormBoundary5rEpBecoRZ2tj60k为分割符,每个两个分割符之前对于一个参数。 ![img](datas\images\上传文件参数03.png) **python进行请求** ```python # 请求头 ''' 这里注意,要将Content-Type注释掉。因为在请求的时候,会自动加上。 ''' header = { 'Authorization': '1677034306556', 'Connection': 'keep-alive', # 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryFXTT4S1LKA1LUDBd', 'Cookie': 'SHIROJSESSIONID=75ace860-0f00-4db0-9440-6c6d53cdf101', 'Host': 'host:8088', 'Origin': 'http://host:8088', 'Referer': 'http://host:8088/njfxq/search/clue/clueFeedBackDetailAll?id=1574192996457648130&Paramspage=clue&caseId=1567439544410976257', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36' } # 请求体Payload ''' 这里有必要解释下: 如果请求体按照页面显示的配置如下: fileObject = { 'type':'6', 'orgType': 'B', 'file': open('上传文件.xlsx','rb') } 是错误的(第一次花费半天才调通) // 正确的格式应该是传入一个元组,格式为:(,,) ,这里的fileObject是指具体的值。 正确的请求体应为: fileObject = { 'type':(None,'6',None), 'orgType': (None,'B',None), 'file': ('上传文件.xlsx',open('上传文件.xlsx','rb'),'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') } ''' fileObject = { 'type':(None,'6',None), 'orgType': (None,'B',None), 'file': ('上传文件.xlsx',open('上传文件.xlsx','rb'),'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') } req = requests.post('http://host:8088/njfxq/finance/investigatefeedback/uploadFile',headers=header,files=fileObject) print(req.text) ``` **总结** ​ Payload请求体如何转换的问题,看下图应该比较容易理解。 ```python # 下面为补充后的Payload ------WebKitFormBoundarynS4EDa2hdT8tfnF8 Content-Disposition: form-data; name="type"; filename=None content-type: None 6 # type的值 ------WebKitFormBoundarynS4EDa2hdT8tfnF8 Content-Disposition: form-data; name="orgType"; filename=None content-type: None B # orgType的值 ------WebKitFormBoundarynS4EDa2hdT8tfnF8 Content-Disposition: form-data; name="file"; filename="样本标签.xlsx" Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet fileObject # 为文件的二进制数据 ------WebKitFormBoundarynS4EDa2hdT8tfnF8-- # 转换为python的请求格式 格式为:'name':(,,) # 对比如下 fileObject = { 'type':(None,'6',None), 'orgType': (None,'B',None), 'file': ('上传文件.xlsx',open('上传文件.xlsx','rb'),'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') } ``` **编写用例** 注意!!!请求头不要填写类似 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryFXTT4S1LKA1LUDBd' 的这段参数,否则请求会出错 ``` file_01: host: '$func{{get_host(oa)}}' path: '/api/oaUserCenterManager/userCenterConfig/consumptionList/update' method: 'POST' detail: '文件上传' headers: cookie: '$cache{{loginOaCookie}}' requestPayloadType: 'UPLOAD' requestPayload: # name 即为 Content-Disposition 中的name。必填项 # value 即为 fileObject ,如果是参数就填具体的值,若是文件,则填对应的文件名(需要放在指定目录下)。必填项 # type 即为 Content-Type。非必填项 # isFile 说明当前参数是否为文件。非必填项,不填默认为非文件参数 - name: 'file' value: '123.xls' type: 'application/vnd.ms-excel' isFile: True - name: 'op' value: 'import' - name: 'data' value: '{"useType":1}' - name: 'token' value: '$cache{{loginOaToken}}' isRun: True ``` #### 13、如何测试下载文件接口 13.1)在yaml文件中,我们需要将 requestPayloadType 填写为 download 13.2)一般下载文件的请求方式常为下面两种,一种是post,另一种直接用url通过get方式请求下载: ``` file_02: host: '$func{{get_host(oa)}}' path: '/api/oaUserCenterManager/myWorkSpace/export/select' method: 'POST' detail: '文件下载' headers: cookie: '$cache{{loginOaCookie}}' requestPayloadType: 'DOWNLOAD' requestPayload: data: "{\"id\":2322175,\"type\":\"passengerBalanceUseLimitImport\",\"filename\":\"乘客限制名单模板.xls_20230705142143032\"}" menuId: 16846 menuName: "/portrayal/fileDownload" op: "download" systemName: "乘客画像系统" token: '$cache{{loginOaToken}}' isRun: True file_03: host: 'https://alifei01.cfp.cn/' path: '/creative/vcg/800/version23/VCG41175510742.jpg' method: 'GET' detail: '文件下载URL' headers: requestPayloadType: 'DOWNLOAD' requestPayload: isRun: True ``` 下载后的文件,存在在 datas\files\download 文件夹中,命名方式为 “caseId + 文件名”,以避免用例间下载了相同的文件从而被覆盖: ![image-20230710152144211](datas\images\下载后的文件存放位置.png) #### 14、如何在程序运行中将当前用例的系统缓存数据删除 程序中提供了currentCaseDeleteCache这个关键字,可以在当前这条用例结束后,删除系统缓存数据 如下案例所示: ``` # currentCaseDeleteCache:删除系统缓存。运行节点为在当前用例断言结束之后 currentCaseDeleteCache: - delType: 'eq' delData: - 'search_01_teardown_search_03_response_uid' - 'search_01_teardown_search_03_response_uid' - delType: 're' delData: - '^search_01_setup' - 'request_uid$' ``` - delType:提供两种类型,eq表示配置相同的delData缓存名称进行删除,re表示通过正则表达是配置符合条件的缓存名称进行删除 - delData:字符串列表的形式去匹配每一个符合条件的缓存名称 ## 其他内容 ### 测试流程 ​ 1、可行性分析 ​ 确认其可行性,是否可以实行测试自动化 ​ 2、测试需求分析 ​ 确定测试覆盖率以及自动化测试粒度、测试用例上的筛选等 ​ 3、制定测试计划 ​ 评估完成所有测试活动的时间,测试活动安排及资源分配等,控制测试过程以及跟踪整个测试过程 ​ 4、测试用例设计 ​ 通过从功能测试用例筛选、修改,转为自动化测试用例 ​ 5、测试脚本开发 ​ 框架设计与搭建,测试脚本编写,Git版本控制,脚本合并联调 ​ 6、测试执行阶段 ​ 脚本运行环境搭建,手动、半手动半自动方式执行或Jenkins无人值守自动执行 ​ 7、测试总结阶段 ​ 对测试结果文件中报告错误的记录进行分析,如果确实是由于被测系统的缺陷导致,则提交缺陷报告。对自动化测试的结果进行总结,分析系统存在的问题,并提交《测试报告》 ​ 8、脚本维护迭代 ## 演示图片 #### 业务系统 #### 测试用例 #### 测试报告