# API_auto_v2 **Repository Path**: chen_jiabao/api_auto_v2 ## Basic Information - **Project Name**: API_auto_v2 - **Description**: 本框架主要是基于 Python + pytest + allure + log + yaml + mysql + redis + 钉钉 通知 实现的接口自动化框架。 用例基于yaml 配置,无需编写流程代码 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-12-05 - **Last Updated**: 2023-12-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 框架介绍 本框架主要是基于 Python + pytest + allure + log + yaml + mysql + redis + 钉钉 通知 实现的接口自动化框架。 ## 前言 采用pageobject模式 进行用例管理,用例与接口逻辑层隔离,扩展性强,无需编写业务代码即可编写用例,用例逻辑通过配置的yaml文件进行内维护,支持用例多种参数化,避免了重复编写用例,用例耦合 ## 实现功能 - 测试用例 与 测试接口 隔离: 采用Service Object模式,支持复杂场景测试,扩展性强 - 支持复杂依赖场景 - 动态多断言:根据断言类型自动断言 - 断言类型:支持所有断言场景,十六种断言类型 - 自动生成接口数据:通过读取PD文件,写入接口数据进yaml (待实现) - 函数运行时间监控 - 企微通知推送 - 用例调用接口时,每个接口自定义参数 - 自定义拓展字段: 如用例中需要生成的随机数据,可直接调用 - pytest 多线程执行 - 执行环境:支持多域名, 多数据库连接,多数据库,多redis 组合使用 - 日志模块: 主要模块都添加了日志记录 - 钩子函数:针对无法用框架自定义处理的部分,可增加自定义方法增加代码处理 - 参数变量:任何参数配置都支持mysql,redis,结果配置,变量标识可复用其他字段 - 数据驱动:单用例配置数据驱动后,在执行中会解析多条用例运行 ## 目录结构 - config // 配置 - config.yaml // 公共配置 - db_command.yaml //sql ,redis_key 配置 - data - api_pool // api 目录 - room_api.yaml // api 文件 - case_pool // 用例目录 - room_case.yaml //用例文件 - report // allure测试报告 - tmp // allure测试报告 - testCase - test_demo.py // 用例执行 - utils - assertion //断言处理 - assert_control.py //断言逻辑 - comparators.py // 断言类型 - cache_tools // 缓存 - cacheout_control.py - cache_control.py // 文件缓存 - redis_control.py // redis缓存方法 - data_handle //数据处理 - case_format_con.py // 旧版接口格式转新版格式 - data_matching.py // jsonpath匹配 - making_data_tools.py // 假数据 - replace_params.py // 参数替换的逻辑处理 - file_tools - path_control.py // 路径操作 - root_path.py // 根目录 - yaml_control.py //yaml读写 - logging_tools - logger.py //日志 - log_of_output.py //打印接口数据 - run_time_duration.py // 统计函数运行时长 - mysql_tools - mysql_control.py //mysql - other_tools - models.py //映射字段模型 - proj_control.py // 环境匹配逻辑 - test_case_check.py // 用例字段检查是否合法 - time_control.py //时间 - abnormal - exceptions.py //自定义异常类 - exception_control.py //异常装饰器 - allure_tools - allure_data_clean.py // 解析allure数据生成测试报告 - allure_hook.py - allure_tools.py // 提交数据到allure - business // 业务方面 - case_generator.py // 用例生成器 - get_url_qrcode.py // 生成指定url的二维码 - GUI_tools.py // 可视化页面 - request_tools //请求 - common_case_handle.py // 前置处理和后置处理父类 - make_hook.py //钩子函数装饰器 - preproc_handle.py // 用例前置处理 - reqeust_hook_control.py // 钩子函数执行逻辑 - request_control.py // 请求逻辑 - teardown_handle.py // 后置处理 - hook_request.py // 钩子函数逻辑层 - sys_control.py // 本地操作 - conftest.py // pytest 钩子函数 - environment.properties // 增加到allure的环境信息 - main.py - pytest.ini // pytest执行配置 - requirements.txt // 项目依赖的库 - run.py // 启动项目 ## 安装流程 搭建python教程:[http://c.biancheng.net/view/4161.html](https://gitee.com/link?target=http%3A%2F%2Fc.biancheng.net%2Fview%2F4161.html) 搭建jdk环境:[https://www.cnblogs.com/zll-wyf/p/15095664.html](https://gitee.com/link?target=https%3A%2F%2Fwww.cnblogs.com%2Fzll-wyf%2Fp%2F15095664.html) 安装allure:[https://blog.csdn.net/m0_49225959/article/details/117194318](https://gitee.com/link?target=https%3A%2F%2Fblog.csdn.net%2Fm0_49225959%2Farticle%2Fdetails%2F117194318) allure: cmd窗口下 allure --version命令,能显示版本就配置成功 如上环境如都搭建好,则安装本框架的所有第三方库依赖,执行如下命令: ``` pip3 install -r requirements.txt ``` 编写用例流程: ```mermaid graph LR A[批量导入所有接口到yaml文档] -->| | B(在yaml内编写接口执行逻辑) ``` ## 如何创建用例 ### 案例解读 #### 模型说明 ``` // 测试用例 model class TestCase(BaseModel): t_request: Union[TRequest, None] = None //用例包含的接口数据 来自TRequest model db: Union[Text, None] = "mizos" // 该用例要使用的数据库名称 detail: Union[Text, None] = None //用例描述 is_run: Union[bool, None] = True //是否执行用例 dependence_case_data: Union[None, List, "DependentCaseData"] = None //依赖的数据 来自DependentCaseData model asserts: Union[None, List] = None //断言配置 current_request_set_cache: Union[Text] // 缓存名称 proj_env: Union[Text, bool, None] = "staging" // 用例执行环境 dependency_sql: Union[bool, None, Dict] = None //前置依赖的sql 该参数结果可用例内任意参数使用 teardown_sql: Union[List[Text], bool, None] = None //后置依赖的sql dependency_redis: Union[Dict, bool, None] = None //前置依赖的redis_key teardown_redis: Union[Dict, bool, None] = None //后置依赖的redis_key sleep: Union[int, float, None] = None //执行该用例时,是否需要等待 teardown_dependency: Union[Dict, None, bool, None, Text] = None //执行该用例后 需要后置执行的接口 preproc_hook: Union[Text, None] = None //前置依赖的hook 配置的函数重新格式化数据去请求 teardown_hook: Union[Text, None] = None //后置依赖的hook 配置的函数重新格式化响应数据去断言 ``` ``` // 接口请求model,同requests.Request可选参数 class TRequest(BaseModel): """requests.Request model""" method: Text url: Text params: Dict[Text, Text] = {} headers: Union[Dict[Any, Any], None, Text] = {} req_json: Union[Dict, List, Text] = Field(None, alias="json") data: Union[Text, Dict[Text, Any], None] = {} cookies: Dict[Text, Text] = {} timeout: float = 1200 allow_redirects: bool = True verify: bool = True files: Union[Dict, None] = {} auth: Union[Dict, None] = {} proxies: Union[Dict, None] = {} ``` #### 用例配置 测试用例配置: ``` get_native_login_verify_case: detail: 注册账号流程 dependence_case_data: - test_case_id: get_verification_code dependent_data: - jsonpath: $.resp_json.key replace_key: sms_code passing_params: - phone_no: '${phone_no}' country: ${country} - test_case_id: get_native_register passing_params: - phone_no: '${phone_no}' passwd: Aa123123 country: '${country}' sms_code: '${sms_code}' - test_case_id: get_reset_passwd passing_params: - phone_no: '${phone_no}' passwd: Aa123123 country: ${country} - test_case_id: get_native_login_verify passing_params: - phone_no: '${phone_no}' asserts: - check_value: $.resp_json type: contains expect_value: user_id message: 验证注册流程是否正确 current_request_set_cache: get_native_login_verify_case preproc_hook: ${hook_get_mobile_phone_num} ``` !!#ff0000 detail: 描述接口执行场景!! !!#ff0000 dependence_case_data: 依赖处理!! !!#ff0000 test_case_id: 依赖的接口名称!! !!#ff0000 dependent_data:依赖接口的数据 !! !!#ff0000 jsonpath: 提取依赖接口数据的JSONPATH表达式!! !!#ff0000 passing_params: 替换该接口对应接口内的字段 !! !!#ff0000 current_request_set_cache:该用例标识的缓存名称 !! !!#ff0000 preproc_hook:前置钩子函数 !! #### 执行逻辑(附件有执行过程的日志打印记录) 1. 会先将该用例 实例成TestCase Model 2. 处理preproc_hook字段,因为注册都需要未注册的手机号,所以在这里自定义代码处理数据,增加未注册过的手机号到流程中(该字段映射的值是一个函数名称,该函数在执行用例前会接收一个 TestCase Model对象,并返回该对象,在函数内可以自由处理用例内所有数据) 3. 按照顺序处理dependence_case_data中的依赖数据 4. 处理第一组数据(test_case_id: get_verification_code),会从缓存内找出get_verification_code接口数据并序列成TestCase model 并使用 passing_params内配置的参数替换源参数 5. get_verification_code 用例执行时,无前置依赖(dependence_case_data,preproc_hook等),开始请求接口,并返回结果 6. 存在dependent_data字段时,会按照 jsonpath字段的配置提取数据,并替换下面依赖的其他接口传递的参数replace_key 7. 依次按照逻辑处理: get_native_register,get_reset_passwd,get_native_login_verify 8. 处理 passing_params 内的 asserts字段,该字段符合testcase model的映射字符,在实例时不会接口参数传入,而是实例成 testcase model的字段,在执行get_native_login_verify接口时,会执行配置的断言 ### 编写断言方法 ``` // 断言模型 class Assert(BaseModel): check_value: Any //检查值 type: AssertMethod //断言类型 expect_value: Any //预期值 message: Text //断言失败的提示语 ``` #### check_value and expect_value: 支持 sql断言, reids_key断言, 实参断言,jsonpath断言,根据填入的数据,程序自动判断类型 1. !!#ff0000 如何使用jsonpath !! !!#ff0000 jsonpath提取的数据结构:!! ``` { "res_data": 当前接口的请求数据, "res_headers": 当前接口的请求头数据, "resp_json": 当前接口的响应数据 } ``` !!#ff0000 如果 resp_json value 为 {"name": "123","id" : "456"}!! !!#ff0000 获取响应数据的id字段: $.resp_json.id !! 2. !!#00ff00 如何使用sql,redis_key 的结果!! !!#00ff00 方式一: 填写对应的sql 语句,redis命令 (不推荐)!! !!#00ff00 方式二:在db_command.yaml文件内写入语句并辨识key,在用例内引用key,增加语句复用率,key已sql_ 或 redis_开头!! ``` //demo sql_get_user_equals_1469810: SELECT Id FROM `mizos`.`account` WHERE `Id` = '1469810' LIMIT 0,1000 sql_get_user_equals_1470981: SELECT Id FROM `mizos`.`account` WHERE `id` = '1470981' LIMIT 0,1000 sql_get_user_equals_1449564: SELECT Id FROM `mizos`.`account` WHERE `id` = '1449564' LIMIT 0,1000 ``` ####type: 根据实际情况选择接口的断言方式 ``` @unique class AssertMethod(Enum): """ 断言类型 == 等于 lt 小于 le 小于等于 gt 大于 ge 大于等于 not_eq 不等于 str_eq str类型等于 len_eq 长度相等 len_gt 长度大于 len_ge 小于等于 len_lt 长度小于 len_le 长度大于等于 contains 检查结果 是否包含 预期结果 contained_by 预期结果 是否包含 检查结果 startswith 字符串的检查结果 是否已 预期结果 开头 endswith 字符串的检查结果 是否已 预期结果 结束 """ equal = "==" less_than = "lt" less_or_equals = "le" greater_than = "gt" greater_or_equals = "ge" not_equal = "not_eq" string_equals = "str_eq" length_equal = "len_eq" length_greater_than = "len_gt" length_less_or_equals = 'len_ge' length_less_than = "len_lt" length_greater_or_equals = 'len_le' contains = "contains" contained_by = 'contained_by' startswith = 'startswith' endswith = 'endswith' ``` ### 编写钩子函数: 1.函数名称以hook_开头 2.需要在hook_request.py 文件内定义钩子函数 3.需要检查定义的函数名称与用例配置的preproc_hook是否一致 4.函数会接收到 TestCase model 的全部数据,并返回全部数据,可以自定义处理数据 5.替换testcase数据时,使用replace_data_fields函数,传入需要处理的字典,和需要替换的key和value,字典格式 ``` //替换数据写法 yaml_case = replace_data_fields(yaml_case.dict(), {"phone_no": "1" + random_number, "country": 86}) ``` 下面实现了一个完成的函数,实现自动生成了未注册的手机号(demo是简单模拟),并替换数据返回 ``` // 完整钩子函数demo, def hook_get_mobile_phone_num(yaml_case: TestCase) -> TestCase: random_number = str(random.randrange(1000010000, 9999919999)) replace_params = {"phone_no": "1" + random_number, "country": 86} yaml_case = replace_data_fields(yaml_case.dict(), replace_params) return TestCase(**yaml_case) ``` ### 项目环境说明 项目多环境,多库,是通过 TestCase.db TestCase.proj_env 判断的,默认值是db=mizos,proj_env=staging其他环境需要指定,如果使用这两个参数就不用配置,程序会根据这两个字段获取mysql,redis,url 配置 ### 接口数据说明 接口数据沿用之前旧版的模式,可兼容之前写的所有接口信息,接口数据会实例 TRequest模型,可为接口单独配置超时时间,代理,等信息 ### 用例执行 通过test_mian一个函数来执行所有用例,因为程序逻辑都是内部完成的,不需要用到pytest方法,只需要pytest标记用例,所以在这里参数化了所有用例,下面是main方法 ``` @pytest.mark.parametrize("get_tests_data", **get_tests_from_yaml()) def test_main(self, get_tests_data): resp_data = RequestControl(get_tests_data).prepare_request_flow() AssertControl(resp_data).assert_type_handle_process() ``` 实际用例数据逻辑如下,可指定用例名执行,用例文件执行,所有用例执行等多种模式 ``` def get_tests_from_yaml(): def test_case_config(): """ file_name: 指定单个文件或多个文件,并执行指定文件内的用例, 空字符串默认执行所有文件用例 :return file_test_case_id case_name: 指定单个或者多个用例名称,并执行指定用例 :return test_case_id """ file_name = [] file_test_case_id = [] for file in file_name: test_case_id = YamlHandle(file).get_yaml_all_key() file_test_case_id.extend(test_case_id) test_case_id = ["multiplayer_enter_room_case"] test_case_all = CaCheControl().get_test_case_all() return test_case_id case_list = test_case_config() case_ids, case_data_list = [], [] for case in case_list: case_data = CaCheControl.get_cache_data(case) case_data_list.append(case_data) case_ids.append(case_data.get(TestCaseEnum.DETAIL.value[0], "未定义的接口")) test_params = { "argvalues": case_data_list, "ids": case_ids } return test_params ``` ### 编写用例流程 1. 用例名称与current_request_set_cache字段一致 2. 编写dependence_case_data字段内容,按照顺序第一个请求的接口名称(test_case_id),替换该接口的哪些参数(passing_params),提取该接口数据,并应用于哪个字段(dependent_data) 3. 如需要请求多个接口,即配置多个接口名称,接口参数可使用sql,redis_key,hook,或 实参来作为参数 4. 断言需要在passing_params字段内增加asserts字段,作为当前接口下的断言 5. 在test_case 层执行 test_main方法,并替换其中的用例ID来执行用例