commit
8fa5930e90
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -62,4 +62,6 @@ lark:
|
|||
webhook:
|
||||
|
||||
|
||||
|
||||
# 定义重跑参数
|
||||
reruns: 3
|
||||
reruns_delay: 3
|
|
@ -0,0 +1,48 @@
|
|||
case_common:
|
||||
allureEpic: 上传通用接口
|
||||
allureFeature: 上传接口
|
||||
allureStory: 上传接口
|
||||
|
||||
upload_file_01:
|
||||
host: http://test-admin-api.jekunauto.net
|
||||
url: /v1/upload/create
|
||||
method: POST
|
||||
detail: 上传接口
|
||||
headers:
|
||||
Content-Type: 'multipart/form-data'
|
||||
Authorization: 'Jekun 0013936:JHC9i8pAh3:zec96hsUSd0v17q9eM2x0WEy740='
|
||||
Entity-Date: 'Fri, 24 Feb 2023 07:23:48 GMT'
|
||||
User-Agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'
|
||||
|
||||
|
||||
# 这里cookie的值,写的是存入缓存的名称
|
||||
# 请求的数据,是 params 还是 json、或者file、data
|
||||
requestType: file
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
file:
|
||||
file: '排入水体名.png'
|
||||
data:
|
||||
act: '232'
|
||||
params:
|
||||
fields: '*'
|
||||
|
||||
dependence_case: False
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
- case_id:
|
||||
dependent_data:
|
||||
- dependent_type:
|
||||
jsonpath:
|
||||
set_cache:
|
||||
|
||||
assert:
|
||||
# 断言接口状态码
|
||||
status_code: 200
|
||||
data:
|
||||
jsonpath: $.data.act
|
||||
type: ==
|
||||
value: '232'
|
||||
AssertType:
|
||||
sql:
|
|
@ -0,0 +1,154 @@
|
|||
case_common:
|
||||
allureEpic: JekunAuto小程序接口
|
||||
allureFeature: 商城模块
|
||||
allureStory: 商城模块
|
||||
|
||||
platform_index_01:
|
||||
host: ${{host()}}
|
||||
url: /v1/category/platform-index
|
||||
method: GET
|
||||
detail: 商城主页
|
||||
headers:
|
||||
Connection: 'keep-alive'
|
||||
clientSource: '0'
|
||||
areaCode: '440105'
|
||||
content-type: 'application/json'
|
||||
Cookie: '_csrf-frontend=d096dba5839b9b40dbe5272fc4e4957f6b1b184d8fb7eaebc21cd75627ef2bffa%3A2%3A%7Bi%3A0%3Bs%3A14%3A%22_csrf-frontend%22%3Bi%3A1%3Bs%3A32%3A%2293xDpdFnQnR8bB2icCtVFXIALxa_KJzO%22%3B%7D; advanced-frontend=m5ls5co6orfgd1r2147cild98g'
|
||||
# 这里cookie的值,写的是存入缓存的名称
|
||||
# 请求的数据,是 params 还是 json、或者file、data
|
||||
requestType: params
|
||||
# 是否执行,空或者 true 都会执行
|
||||
is_run:
|
||||
data:
|
||||
fields: "id,categoryName,childCategories"
|
||||
thirdPartySource: JEKUNAUTO
|
||||
clientSource: "0"
|
||||
dependence_case: False
|
||||
# 依赖的数据
|
||||
dependence_case_data:
|
||||
- case_id: user_info_01
|
||||
dependent_data:
|
||||
- dependent_type: response
|
||||
jsonpath: $.data[userId]
|
||||
set_cache: userId
|
||||
- dependent_type: response
|
||||
jsonpath: $.data[accessId]
|
||||
set_cache: accessId
|
||||
- dependent_type: response
|
||||
jsonpath: $.data[accessKey]
|
||||
set_cache: accessKey
|
||||
|
||||
assert:
|
||||
# 断言接口状态码
|
||||
status_code: 200
|
||||
data:
|
||||
jsonpath: $.data[0].categoryName
|
||||
type: ==
|
||||
value: "车身护理"
|
||||
AssertType:
|
||||
sql:
|
||||
|
||||
|
||||
platform_property_02:
|
||||
host: ${{host()}}
|
||||
url: /v1/service/service/property
|
||||
method: get
|
||||
detail: 获取商城筛选字段
|
||||
headers:
|
||||
Connection: 'keep-alive'
|
||||
clientSource: '0'
|
||||
areaCode: '440105'
|
||||
content-type: 'application/json'
|
||||
requestType: params
|
||||
is_run:
|
||||
data:
|
||||
fields: "goodsProperty,isNeedMatchCar,matchProperty,sort"
|
||||
thirdPartySource: JEKUNAUTO
|
||||
carLicense:
|
||||
frontServiceId: 1
|
||||
dependence_case:
|
||||
dependence_case_data:
|
||||
|
||||
assert:
|
||||
status_code: 200
|
||||
data:
|
||||
jsonpath: $.data.*
|
||||
type: contains
|
||||
value: "价格从高至低"
|
||||
AssertType:
|
||||
sql:
|
||||
|
||||
platform_goods_list_03:
|
||||
host: ${{host()}}
|
||||
url: /v1/service/service/platform-goods-list
|
||||
method: get
|
||||
detail: 获取商品详情
|
||||
headers:
|
||||
Connection: 'keep-alive'
|
||||
clientSource: '0'
|
||||
areaCode: '440105'
|
||||
content-type: 'application/json'
|
||||
requestType: params
|
||||
is_run:
|
||||
data:
|
||||
fields: "goods,after"
|
||||
thirdPartySource: JEKUNAUTO
|
||||
carLicense: "粤AAAA02"
|
||||
frontServiceId: 1
|
||||
goodsPropertyItemIds: []
|
||||
dependence_case:
|
||||
dependence_case_data:
|
||||
|
||||
assert:
|
||||
status_code: 200
|
||||
sql:
|
||||
|
||||
|
||||
platform_goods_comment_04:
|
||||
host: ${{host()}}
|
||||
url: /v1/order/order-goods-comment/latest
|
||||
method: get
|
||||
detail: 商品最新评价
|
||||
headers:
|
||||
Connection: 'keep-alive'
|
||||
clientSource: '0'
|
||||
areaCode: '440105'
|
||||
content-type: 'application/json'
|
||||
from: 1082943R5J4DKfcE
|
||||
requestType: params
|
||||
is_run:
|
||||
data:
|
||||
fields: "*"
|
||||
thirdPartySource: JEKUNAUTO
|
||||
goodsId: "1"
|
||||
dependence_case:
|
||||
dependence_case_data:
|
||||
|
||||
assert:
|
||||
status_code: 200
|
||||
sql:
|
||||
|
||||
|
||||
platform_goods_total_05:
|
||||
host: ${{host()}}
|
||||
url: /v1/order/order-goods-comment/latest
|
||||
method: get
|
||||
detail: 获取商品total
|
||||
headers:
|
||||
Connection: 'keep-alive'
|
||||
clientSource: '0'
|
||||
areaCode: '440105'
|
||||
content-type: 'application/json'
|
||||
from: 1082943R5J4DKfcE
|
||||
requestType: params
|
||||
is_run:
|
||||
data:
|
||||
fields: "*"
|
||||
thirdPartySource: JEKUNAUTO
|
||||
goodsId: "1"
|
||||
dependence_case:
|
||||
dependence_case_data:
|
||||
|
||||
assert:
|
||||
status_code: 404
|
||||
sql:
|
|
@ -179,5 +179,4 @@ user_info_car_01:
|
|||
assert:
|
||||
# 断言接口状态码
|
||||
status_code: 200
|
||||
|
||||
sql:
|
||||
sql:
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023-03-10 11:03:09
|
||||
|
||||
|
||||
import allure
|
||||
import pytest
|
||||
from utils.read_files_tools.get_yaml_data_analysis import GetTestCase
|
||||
from utils.assertion.assert_control import Assert
|
||||
from utils.requests_tool.request_control import RequestControl
|
||||
from utils.read_files_tools.regular_control import regular
|
||||
from utils.requests_tool.teardown_control import TearDownHandler
|
||||
|
||||
|
||||
case_id = ['upload_file_01']
|
||||
TestData = GetTestCase.case_data(case_id)
|
||||
re_data = regular(str(TestData))
|
||||
|
||||
|
||||
@allure.epic("上传通用接口")
|
||||
@allure.feature("上传接口")
|
||||
class TestUploadFile:
|
||||
|
||||
@pytest.mask.flaky(reruns=3, reruns_delay=3)
|
||||
@allure.story("上传接口")
|
||||
@pytest.mark.parametrize('in_data', eval(re_data), ids=[i['detail'] for i in TestData])
|
||||
def test_upload_file(self, in_data, case_skip):
|
||||
"""
|
||||
:param :
|
||||
:return:
|
||||
"""
|
||||
res = RequestControl(in_data).http_request()
|
||||
TearDownHandler(res).teardown_handle()
|
||||
Assert(in_data['assert_data']).assert_equality(response_data=res.response_data,
|
||||
sql_data=res.sql_data, status_code=res.status_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['test_upload_file.py', '-s', '-W', 'ignore:Module already imported:pytest.PytestWarning'])
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023-03-10 11:03:09
|
||||
|
||||
import allure
|
||||
import pytest
|
||||
from utils.read_files_tools.get_yaml_data_analysis import GetTestCase
|
||||
from utils.assertion.assert_control import Assert
|
||||
from utils.requests_tool.request_control import RequestControl
|
||||
from utils.read_files_tools.regular_control import regular
|
||||
from utils.requests_tool.teardown_control import TearDownHandler
|
||||
import pytest_rerunfailures
|
||||
|
||||
case_id = ['platform_index_01', 'platform_property_02', 'platform_goods_list_03', 'platform_goods_comment_04', 'platform_goods_total_05']
|
||||
TestData = GetTestCase.case_data(case_id)
|
||||
re_data = regular(str(TestData))
|
||||
|
||||
|
||||
@allure.epic("JekunAuto小程序接口")
|
||||
@allure.feature("商城模块")
|
||||
class TestPlatformIndex:
|
||||
|
||||
@allure.story("商城模块")
|
||||
@pytest.mark.flaky(reruns=3, reruns_delay=3)
|
||||
@pytest.mark.parametrize('in_data', eval(re_data), ids=[i['detail'] for i in TestData])
|
||||
def test_platform_index(self, in_data, case_skip):
|
||||
"""
|
||||
:param :
|
||||
:return:
|
||||
"""
|
||||
res = RequestControl(in_data).http_request()
|
||||
TearDownHandler(res).teardown_handle()
|
||||
Assert(in_data['assert_data']).assert_equality(response_data=res.response_data,
|
||||
sql_data=res.sql_data, status_code=res.status_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['test_platform_index.py', '-s', '-W', 'ignore:Module already imported:pytest.PytestWarning'])
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023-02-15 17:45:07
|
||||
# @Time : 2023-03-10 11:03:09
|
||||
|
||||
|
||||
import allure
|
||||
|
@ -20,7 +20,8 @@ re_data = regular(str(TestData))
|
|||
@allure.epic("JekunAuto小程序接口")
|
||||
@allure.feature("登录模块")
|
||||
class TestLogin:
|
||||
|
||||
|
||||
@pytest.mask.flaky(reruns=3, reruns_delay=3)
|
||||
@allure.story("登录")
|
||||
@pytest.mark.parametrize('in_data', eval(re_data), ids=[i['detail'] for i in TestData])
|
||||
def test_login(self, in_data, case_skip):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023-02-15 17:45:07
|
||||
# @Time : 2023-03-10 11:03:09
|
||||
|
||||
|
||||
import allure
|
||||
|
@ -20,7 +20,8 @@ re_data = regular(str(TestData))
|
|||
@allure.epic("JekunAuto小程序接口")
|
||||
@allure.feature("登录模块")
|
||||
class TestUserInfo:
|
||||
|
||||
|
||||
@pytest.mask.flaky(reruns=3, reruns_delay=3)
|
||||
@allure.story("用户信息")
|
||||
@pytest.mark.parametrize('in_data', eval(re_data), ids=[i['detail'] for i in TestData])
|
||||
def test_user_info(self, in_data, case_skip):
|
||||
|
|
|
@ -23,6 +23,7 @@ class Assert:
|
|||
# literal_eval()函数:会判断需要计算的内容计算后是不是合法的python类型,如果是则进行计算,否则不计算,就是去''
|
||||
self.assert_data = ast.literal_eval(cache_regular(str(assert_data)))
|
||||
# 返回一个字典,{Key:value} value的类型是一个函数或者是一个@staticmethod的静态方法
|
||||
# {'equals': <function equals at 0x000001F226474670>,'less_than': <function less_than at 0x000001F2264772E0>, 'less_than_or_equals': <function ......'endswith': <function endswith at 0x000001F226477AC0>}
|
||||
self.functions_mapping = load_module_functions(assert_type)
|
||||
|
||||
@staticmethod
|
||||
|
@ -115,6 +116,8 @@ class Assert:
|
|||
resp_data=resp_data,
|
||||
message=message
|
||||
)
|
||||
if len(resp_data) > 1:
|
||||
str(resp_data)
|
||||
|
||||
# 判断assertType为空的情况下,则走响应断言
|
||||
elif assert_types is None:
|
||||
|
@ -143,13 +146,10 @@ class Assert:
|
|||
assert status_code == values
|
||||
else:
|
||||
assert_value = self.assert_data[key]['value'] # 获取 yaml 文件中的期望value值
|
||||
print("---------yaml的断言数据assert_value:{}----------------".format(assert_value))
|
||||
assert_jsonpath = self.assert_data[key]['jsonpath'] # 获取到 yaml断言中的jsonpath的数据
|
||||
assert_types = self.assert_data[key]['AssertType']
|
||||
print("-----------assert_types:{}----------------".format(assert_types))
|
||||
# 从yaml获取jsonpath,拿到对象的接口响应数据
|
||||
resp_data = jsonpath(json.loads(response_data), assert_jsonpath)
|
||||
print("-----------响应数据resp_data:{}----------------".format(resp_data))
|
||||
message = self._message(value=values)
|
||||
# jsonpath 如果数据获取失败,会返回False,判断获取成功才会执行如下代码
|
||||
if resp_data is not False:
|
||||
|
|
|
@ -110,8 +110,8 @@ def length_less_than_or_equals(
|
|||
def contains(check_value: Any, expect_value: Any, message: Text = ""):
|
||||
"""判断期望结果内容包含在实际结果中"""
|
||||
assert isinstance(
|
||||
check_value, (list, tuple, dict, str, bytes)
|
||||
), "expect_value 需要为 list/tuple/dict/str/bytes 类型"
|
||||
check_value, (list, tuple, dict, str, bytes, Text)
|
||||
), "expect_value 需要为 list/tuple/dict/str/bytes Text 类型"
|
||||
assert expect_value in check_value, message
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ from typing import Text, Dict, Callable, Union, Optional, List, Any
|
|||
from dataclasses import dataclass
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class NotificationType(Enum):
|
||||
""" 自动化通知方式 """
|
||||
DEFAULT = 0
|
||||
|
@ -13,6 +14,9 @@ class NotificationType(Enum):
|
|||
FEI_SHU = 4
|
||||
|
||||
|
||||
# dataclass 提供一个简便的方式创建数据类, 默认实现__init__(), __repr__(), __eq__()方法.
|
||||
# dataclass支持数据类型的嵌套
|
||||
# 支持将数据设置为不可变
|
||||
@dataclass
|
||||
class TestMetrics:
|
||||
""" 用例执行数据 """
|
||||
|
@ -36,12 +40,14 @@ class RequestType(Enum):
|
|||
EXPORT = "EXPORT"
|
||||
NONE = "NONE"
|
||||
|
||||
|
||||
# TODO 为什么判断下是否有方法
|
||||
def load_module_functions(module) -> Dict[Text, Callable]:
|
||||
""" 获取 module中方法的名称和所在的内存地址
|
||||
原理:把字典对象转成可遍历对象,判断里面是否有fuction,再组装成字典返回
|
||||
vars()内置函数:参数一个字典对象,返回一个字典
|
||||
。itemns()操作字典函数:字典不可遍历,。items返回一个可遍历的对面,dict_items([('name', 'jack'), ('item', '1')])
|
||||
module_functions:{'equal':<function equals at 0X000***>,'less_than':<function less_than at 0x00**>}
|
||||
|
||||
"""
|
||||
module_functions = {}
|
||||
|
@ -242,3 +248,8 @@ class AssertMethod(Enum):
|
|||
contained_by = 'contained_by'
|
||||
startswith = 'startswith'
|
||||
endswith = 'endswith'
|
||||
|
||||
|
||||
class Reruns(BaseModel):
|
||||
reruns: int
|
||||
reruns_delay: int
|
||||
|
|
|
@ -254,7 +254,7 @@ class CaseData:
|
|||
self.raise_value_null_error(case_id=case_id, data_name="dependence_case")
|
||||
) from exc
|
||||
|
||||
# TODO 对 dependence_case_data 中的值进行验证
|
||||
# 对 dependence_case_data 中的值进行验证
|
||||
def get_dependence_case_data(
|
||||
self,
|
||||
case_id: Text,
|
||||
|
|
|
@ -40,7 +40,8 @@ def write_testcase_file(*, allure_epic, allure_feature, class_title,
|
|||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
# 设置为True的时候,修改yaml文件的用例,代码中的内容会实时更新
|
||||
real_time_update_test_cases = conf_data['real_time_update_test_cases']
|
||||
|
||||
reruns = conf_data['reruns']
|
||||
reruns_delay = conf_data['reruns_delay']
|
||||
page = f'''#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : {now}
|
||||
|
@ -63,7 +64,8 @@ re_data = regular(str(TestData))
|
|||
@allure.epic("{allure_epic}")
|
||||
@allure.feature("{allure_feature}")
|
||||
class Test{class_title}:
|
||||
|
||||
|
||||
@pytest.mark.flaky(reruns={reruns}, reruns_delay={reruns_delay})
|
||||
@allure.story("{allure_story}")
|
||||
@pytest.mark.parametrize('in_data', eval(re_data), ids=[i['detail'] for i in TestData])
|
||||
def test_{func_title}(self, in_data, case_skip):
|
||||
|
|
|
@ -15,6 +15,7 @@ import urllib3
|
|||
from requests_toolbelt import MultipartEncoder
|
||||
from common.setting import ensure_path_sep
|
||||
from utils.logging_tool.log_control import ERROR
|
||||
from utils.other_tools.exceptions import ValueNotFoundError
|
||||
from utils.other_tools.models import RequestType
|
||||
from utils.logging_tool.log_decorator import log_decorator
|
||||
from utils.mysql_tool.mysql_control import AssertExecution
|
||||
|
@ -48,8 +49,8 @@ class RequestControl:
|
|||
_data = self.__yaml_case.data
|
||||
for key, value in ast.literal_eval(cache_regular(str(_data)))['data'].items():
|
||||
file_data[key] = value
|
||||
except KeyError:
|
||||
...
|
||||
except KeyError as exc:
|
||||
raise ValueNotFoundError("{data}数据未找到,请检查yaml文件是否添加") from exc
|
||||
|
||||
@classmethod
|
||||
def multipart_data(
|
||||
|
@ -58,6 +59,7 @@ class RequestControl:
|
|||
""" 处理上传文件数据 """
|
||||
multipart = MultipartEncoder(
|
||||
fields=file_data, # 字典格式
|
||||
# boundary参数 可有可无
|
||||
boundary='-----------------------------' + str(random.randint(int(1e28), int(1e29 - 1)))
|
||||
)
|
||||
return multipart
|
||||
|
@ -386,7 +388,7 @@ class RequestControl:
|
|||
allure_step("响应结果: ", res)
|
||||
|
||||
@log_decorator(True)
|
||||
@execution_duration(3000)
|
||||
@execution_duration(5000)
|
||||
# @encryption("md5")
|
||||
def http_request(
|
||||
self,
|
||||
|
|
|
@ -9,7 +9,6 @@ import time
|
|||
from typing import Text
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def count_milliseconds():
|
||||
"""
|
||||
计算时间
|
||||
|
@ -21,7 +20,7 @@ def count_milliseconds():
|
|||
return access_delta
|
||||
|
||||
|
||||
def timestamp_conversion(time_str: Text) -> int:
|
||||
def timestamp_conversion(time_str: Text) -> float:
|
||||
"""
|
||||
时间戳转换,将日期格式转换成时间戳
|
||||
:param time_str: 时间
|
||||
|
@ -30,15 +29,16 @@ def timestamp_conversion(time_str: Text) -> int:
|
|||
|
||||
try:
|
||||
datetime_format = datetime.strptime(str(time_str), "%Y-%m-%d %H:%M:%S")
|
||||
timestamp = int(
|
||||
time.mktime(datetime_format.timetuple()) * 1000.0
|
||||
+ datetime_format.microsecond / 1000.0
|
||||
)
|
||||
# timestamp = int(
|
||||
# time.mktime(datetime_format.timetuple()) * 1000.0
|
||||
# + datetime_format.microsecond / 1000.0
|
||||
# )
|
||||
timestamp = datetime.timestamp(datetime_format)
|
||||
return timestamp
|
||||
except ValueError as exc:
|
||||
raise ValueError('日期格式错误, 需要传入得格式为 "%Y-%m-%d %H:%M:%S" ') from exc
|
||||
|
||||
|
||||
a = 111
|
||||
def time_conversion(time_num: int):
|
||||
"""
|
||||
时间戳转换成日期
|
||||
|
|
Loading…
Reference in New Issue