diff --git a/README.md b/README.md index 9127457..10e1a77 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 公司突然要求你做自动化,但是没有代码基础不知道怎么做?或者有自动化基础,但是不知道如何系统性的做自动化, 放在yaml文件中维护,不知道如何处理多业务依赖的逻辑? 那么本自动化框架,将为你解决这些问题。 - - 框架主要使用 python 语言编写,结合 pytest 进行二次开发,用户仅需要在 yaml 或者 excel 文件中编写测试用例, 编写成功之后,会自动生成测试用例代码,零基础代码小白,也可以操作。 + - 框架主要使用 python 语言编写,结合 pytest 进行二次开发,用户仅需要在 yaml 文件中编写测试用例, 编写成功之后,会自动生成测试用例代码。 - 如果是具备代码基础的,也可以直接通过 py 文件编写测试用例。 - 使用 Allure 生成报告,并针对测试报告样式进行了调整,使得报告更加美观; - 测试完成后,支持发送 企业微信通知/ 钉钉通知/ 邮箱通知,灵活配置。 @@ -30,7 +30,7 @@ * 支持单独调试用例,支持用例的重复执行 * 框架天然支持接口动态传参、关联灵活处理 * 支持测试数据分析,测试数据不符合规范有预警机制 -* 支持通过用例数据动态配置pytest.mark, 包括自定义标记,pytest.mark.skip以及pytest,mark.usefixtures +* 支持通过用例数据动态配置pytest.mark, 包括自定义标记,pytest.mark.skip以及pytest.mark.usefixtures * 支持利用allure设置用例优先级,运行指定优先级的用例 * 执行环境一键切换,解决多环境相互影响问题 * 自动生成用例代码: 测试人员在yaml/excel文件中填写好测试用例, 程序可以直接生成用例代码,纯小白也能使用 @@ -40,6 +40,7 @@ * 钉钉、企业微信通知: 支持多种通知场景,执行成功之后,可选择发送钉钉、或者企业微信、邮箱通知 * 使用pipenv管理虚拟环境和依赖文件,提供了一系列命令和选项来帮助你实现各种依赖和环境管理相关的操作 * 支持将swagger.json接口文档转为YAML用例 +* 支持接口相应后添加等待时间,方便接口调用后,进行一系列数据初始化操作,待操作成功后,执行后续接口 @@ -48,7 +49,6 @@ allure-pytest = "==2.9.45" click = "==8.1.7" faker = "==21.0.0" -jsonpath = "==0.82.2" loguru = "==0.7.2" openpyxl = "==3.1.2" pydantic = "==2.5.2" @@ -60,7 +60,8 @@ requests-toolbelt = "==1.0.0" sshtunnel = "==0.4.0" xpinyin = "==0.7.6" yagmail = "==0.15.293" -pytest-repeat = "*" +pytest-repeat = "==0.9.3" +jsonpath = "*" ``` @@ -163,6 +164,7 @@ case_info: # 具体的用例数据,是以列表的形式进行管理 request_type: 请求数据类型:params, json, file, data payload: 请求参数 files: 需要上传的文件的相对路径,会自动拼接files目录。 os.path.join(files, "这里传递的files参数值") + wait_seconds: 选填,接口请求后的等待时间,单位是秒。传递错误,会被认为是空 assert_response: 响应断言 assert_sql: 数据库断言 extract: 后置提取参数 diff --git a/interface/pms/open_close_enterprise/test_pms_close_enterprise.yaml b/interface/pms/open_close_enterprise/test_pms_close_enterprise.yaml index df6faa6..5efd26b 100644 --- a/interface/pms/open_close_enterprise/test_pms_close_enterprise.yaml +++ b/interface/pms/open_close_enterprise/test_pms_close_enterprise.yaml @@ -4,9 +4,11 @@ case_common: allure_story: 开通/关闭工作台 case_markers: - pms + - ok - usefixtures: gitlink_login # 前提条件:当前存在gitlink组织,组织已开通工作台 +# (开通工作台后,需要等待一定时间,才能删除工作台成功) case_info: - @@ -43,13 +45,14 @@ case_info: # 开通组织工作台 - pms_open_enterprise_01 teardown: - # 删除组织 - - gitlink_delete_organization_01 + interface: + # 删除组织 + - gitlink_delete_organization_01 - id: pms_close_enterprise_02 title: 密码错误关闭工作台,关闭失败 - run: True + run: true severity: normal url: ${pms_host}/api/pms/pmsEnterprise/${org_identifier} method: DELETE @@ -58,28 +61,28 @@ case_info: cookies: ${cookies} request_type: params payload: - password: ${env_password} + password: 1111111222 files: assert_response: status_code: 200 assertMessage: type_jsonpath: $.msg - expect_value: 操作成功 + expect_value: 密码错误 assert_type: == assertCode: type_jsonpath: $.code - expect_value: 200 + expect_value: 500 assert_type: == assert_sql: extract: case_dependence: + setup: + interface: + # 新建组织,获取组织id + - gitlink_new_organization_01 + # 开通组织工作台 + - pms_open_enterprise_01 teardown: - setup: - interface: - # 新建组织,获取组织id - - gitlink_new_organization_01 - # 开通组织工作台 - - pms_open_enterprise_01 - teardown: + interface: # 删除组织 - - gitlink_delete_organization_01 + - gitlink_delete_organization_01 \ No newline at end of file diff --git a/interface/pms/open_close_enterprise/test_pms_open_enterprise.yaml b/interface/pms/open_close_enterprise/test_pms_open_enterprise.yaml index 92b9cae..c0b76fb 100644 --- a/interface/pms/open_close_enterprise/test_pms_open_enterprise.yaml +++ b/interface/pms/open_close_enterprise/test_pms_open_enterprise.yaml @@ -5,6 +5,7 @@ case_common: case_markers: - pms - debug + - ok - usefixtures: gitlink_login # 前提条件:当前存在gitlink组织,组织未开通工作台 @@ -33,6 +34,7 @@ case_info: userId: ${env_user_id} roleKey: pms_org_admin files: + wait_seconds: 6 assert_response: status_code: 200 assertMessage: diff --git a/utils/case_generate_utils/case_data_analysis.py b/utils/case_generate_utils/case_data_analysis.py index f8cfc9a..a2e530a 100644 --- a/utils/case_generate_utils/case_data_analysis.py +++ b/utils/case_generate_utils/case_data_analysis.py @@ -113,6 +113,7 @@ class CaseDataCheck: 'request_type': self.get_request_type, 'payload': self.case_data.get(TestCaseEnum.PAYLOAD.value[0]), 'files': self.case_data.get(TestCaseEnum.FILES.value[0]), + "wait_seconds": self.case_data.get(TestCaseEnum.WAIT_SECONDS.value[0]), "assert_response": self.case_data.get(TestCaseEnum.ASSERT_RESPONSE.value[0]), "assert_sql": self.case_data.get(TestCaseEnum.ASSERT_SQL.value[0]), 'extract': self.case_data.get(TestCaseEnum.EXTRACT.value[0]), diff --git a/utils/models.py b/utils/models.py index 52605b6..712c144 100644 --- a/utils/models.py +++ b/utils/models.py @@ -87,6 +87,7 @@ class TestCaseEnum(Enum): REQUEST_TYPE = ("request_type", True) PAYLOAD = ("payload", False) FILES = ("files", False) + WAIT_SECONDS = ("wait_seconds", False) EXTRACT = ("extract", False) ASSERT_RESPONSE = ("assert_response", True) ASSERT_SQL = ("assert_sql", False) @@ -108,6 +109,7 @@ class TestCase(BaseModel): run: Union[None, bool, Text] = None payload: Any = None files: Any = None + wait_seconds: int = None extract: Union[None, Dict, Text] = None assert_response: Union[None, Dict, Text] assert_sql: Union[None, Dict, Text] = None diff --git a/utils/requests_utils/request_control.py b/utils/requests_utils/request_control.py index e9db3e2..f535291 100644 --- a/utils/requests_utils/request_control.py +++ b/utils/requests_utils/request_control.py @@ -11,6 +11,7 @@ import json import os import http.cookiejar import copy +import time # 第三方库导入 from requests import Response from loguru import logger @@ -42,6 +43,7 @@ class RequestPreDataHandle: f"请求类型(request_type): {type(request_data.get('request_type', None))} || {request_data.get('request_type', None)}\n" \ f"请求参数(payload): {type(request_data.get('payload', None))} || {request_data.get('payload', None)}\n" \ f"请求文件(files): {type(request_data.get('files', None))} || {request_data.get('files', None)}\n" \ + f"请求后等待(wait_seconds): {type(request_data.get('wait_seconds', None))} || {request_data.get('wait_seconds', None)}\n" \ f"响应断言(assert_response): {type(request_data.get('assert_response', None))} || {request_data.get('assert_response', None)}\n" \ f"数据库断言(assert_sql): {type(request_data.get('assert_sql', None))} || {request_data.get('assert_sql', None)}\n" \ f"后置提取参数(extract): {type(request_data.get('extract', None))} || {request_data.get('extract', None)}\n" \ @@ -60,6 +62,7 @@ class RequestPreDataHandle: self.cookies_handle() self.payload_handle() self.files_handle() + self.wait_seconds_handle() self.assert_handle() self.extract_handle() logger.debug(f"\n======================================================\n" \ @@ -73,6 +76,7 @@ class RequestPreDataHandle: f"请求类型(request_type): {type(self.request_data.get('request_type', None))} || {self.request_data.get('request_type', None)}\n" \ f"请求参数(payload): {type(self.request_data.get('payload', None))} || {self.request_data.get('payload', None)}\n" \ f"请求文件(files): {type(self.request_data.get('files', None))} || {self.request_data.get('files', None)}\n" \ + f"请求后等待(wait_seconds): {type(self.request_data.get('wait_seconds', None))} || {self.request_data.get('wait_seconds', None)}\n" \ f"响应断言(assert_response): {type(self.request_data.get('assert_response', None))} || {self.request_data.get('assert_response', None)}\n" \ f"数据库断言(assert_sql): {type(self.request_data.get('assert_sql', None))} || {self.request_data.get('assert_sql', None)}\n" \ f"后置提取参数(extract): {type(self.request_data.get('extract', None))} || {self.request_data.get('extract', None)}\n" \ @@ -170,6 +174,15 @@ class RequestPreDataHandle: # 将文件处理成绝对路径 self.request_data["files"] = os.path.join(FILES_DIR, files) + def wait_seconds_handle(self): + """ + 处理等待时间参数,如果不能转为int类型,则认为是none + """ + wait_seconds = self.request_data.get("wait_seconds", None) + try: + self.request_data["wait_seconds"] = int(wait_seconds) + except: + self.request_data["wait_seconds"] = None def assert_handle(self): # 处理响应断言参数 assert_response = self.request_data.get("assert_response", None) @@ -208,6 +221,7 @@ class RequestHandle: request_type = kwargs.get("request_type") payload = kwargs.get("payload") files = kwargs.get("files") + wait_seconds = kwargs.get("wait_seconds") status_code = kwargs.get("status_code") response_result = kwargs.get("response_result") response_time_seconds = kwargs.get("response_time_seconds") @@ -238,6 +252,7 @@ class RequestHandle: allure_step(f"请求关键字: {request_type}") allure_step(f"请求参数: {payload}") allure_step(f"请求文件: {files}") + allure_step(f"请求后等待时间: {wait_seconds}") allure_step(f"响应码: {status_code}") allure_step(f"响应结果: {response_result}") allure_step(f"响应耗时: {response_time_seconds} s || {response_time_millisecond} ms") @@ -247,7 +262,11 @@ class RequestHandle: 发送请求并进行后置参数提取操作 """ response = BaseRequest.send_request(self.case_data) - + # 根据配置,增加接口请求等待时间。适应部分调用调用后,需要进行内置数据处理的问题 + logger.debug(f"开始等待") + if self.case_data["wait_seconds"]: + time.sleep(self.case_data["wait_seconds"]) + logger.debug(f"结束等待") self.case_data["status_code"] = response.status_code self.case_data["response_time_seconds"] = round(response.elapsed.total_seconds(), 2) self.case_data["response_time_millisecond"] = round(response.elapsed.total_seconds() * 1000, 2)