pytest-auto-api2-cli 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli.py +9 -0
- common/__init__.py +0 -0
- common/setting.py +78 -0
- pytest_auto_api2/__init__.py +4 -0
- pytest_auto_api2/cli.py +900 -0
- pytest_auto_api2/runtime/__init__.py +1 -0
- pytest_auto_api2/runtime/api.py +10 -0
- pytest_auto_api2/runtime/loader.py +80 -0
- pytest_auto_api2_cli-0.2.0.dist-info/METADATA +945 -0
- pytest_auto_api2_cli-0.2.0.dist-info/RECORD +73 -0
- pytest_auto_api2_cli-0.2.0.dist-info/WHEEL +5 -0
- pytest_auto_api2_cli-0.2.0.dist-info/entry_points.txt +2 -0
- pytest_auto_api2_cli-0.2.0.dist-info/top_level.txt +6 -0
- run.py +85 -0
- test_case/Collect/test_collect_addtool.py +40 -0
- test_case/Collect/test_collect_delete_tool.py +40 -0
- test_case/Collect/test_collect_tool_list.py +40 -0
- test_case/Collect/test_collect_update_tool.py +40 -0
- test_case/Login/test_login.py +40 -0
- test_case/UserInfo/test_get_user_info.py +40 -0
- test_case/__init__.py +6 -0
- test_case/conftest.py +132 -0
- utils/__init__.py +9 -0
- utils/assertion/__init__.py +4 -0
- utils/assertion/assert_control.py +179 -0
- utils/assertion/assert_type.py +141 -0
- utils/cache_process/__init__.py +4 -0
- utils/cache_process/cache_control.py +89 -0
- utils/cache_process/redis_control.py +106 -0
- utils/logging_tool/__init__.py +4 -0
- utils/logging_tool/log_control.py +84 -0
- utils/logging_tool/log_decorator.py +48 -0
- utils/logging_tool/run_time_decorator.py +34 -0
- utils/mysql_tool/__init__.py +4 -0
- utils/mysql_tool/mysql_control.py +175 -0
- utils/notify/__init__.py +1 -0
- utils/notify/ding_talk.py +153 -0
- utils/notify/lark.py +181 -0
- utils/notify/send_mail.py +84 -0
- utils/notify/wechat_send.py +109 -0
- utils/other_tools/__init__.py +0 -0
- utils/other_tools/address_detection.py +73 -0
- utils/other_tools/allure_data/__init__.py +4 -0
- utils/other_tools/allure_data/allure_report_data.py +84 -0
- utils/other_tools/allure_data/allure_tools.py +54 -0
- utils/other_tools/allure_data/error_case_excel.py +316 -0
- utils/other_tools/exceptions.py +47 -0
- utils/other_tools/get_local_ip.py +27 -0
- utils/other_tools/install_tool/__init__.py +0 -0
- utils/other_tools/install_tool/install_requirements.py +91 -0
- utils/other_tools/jsonpath_date_replace.py +28 -0
- utils/other_tools/models.py +269 -0
- utils/other_tools/thread_tool.py +91 -0
- utils/read_files_tools/__init__.py +1 -0
- utils/read_files_tools/case_automatic_control.py +138 -0
- utils/read_files_tools/clean_files.py +19 -0
- utils/read_files_tools/excel_control.py +55 -0
- utils/read_files_tools/get_all_files_path.py +27 -0
- utils/read_files_tools/get_yaml_data_analysis.py +156 -0
- utils/read_files_tools/regular_control.py +209 -0
- utils/read_files_tools/swagger_for_yaml.py +145 -0
- utils/read_files_tools/testcase_template.py +103 -0
- utils/read_files_tools/yaml_control.py +86 -0
- utils/recording/__init__.py +0 -0
- utils/recording/mitmproxy_control.py +225 -0
- utils/requests_tool/__init__.py +0 -0
- utils/requests_tool/dependent_case.py +273 -0
- utils/requests_tool/encryption_algorithm_control.py +80 -0
- utils/requests_tool/request_control.py +443 -0
- utils/requests_tool/set_current_request_cache.py +73 -0
- utils/requests_tool/teardown_control.py +280 -0
- utils/times_tool/__init__.py +0 -0
- utils/times_tool/time_control.py +87 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
# @Time : 2022/5/10 18:54
|
|
5
|
+
# @Author : 余少琪
|
|
6
|
+
# @Email : 1603453211@qq.com
|
|
7
|
+
# @File :
|
|
8
|
+
# @describe:
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import socket
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_host_ip():
|
|
15
|
+
"""
|
|
16
|
+
查询本机ip地址
|
|
17
|
+
:return:
|
|
18
|
+
"""
|
|
19
|
+
_s = None
|
|
20
|
+
try:
|
|
21
|
+
_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
22
|
+
_s.connect(('8.8.8.8', 80))
|
|
23
|
+
l_host = _s.getsockname()[0]
|
|
24
|
+
finally:
|
|
25
|
+
_s.close()
|
|
26
|
+
|
|
27
|
+
return l_host
|
|
File without changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
# @Time : 2022/5/10 14:02
|
|
5
|
+
# @Author : 余少琪
|
|
6
|
+
# @Email : 1603453211@qq.com
|
|
7
|
+
# @File : install_requirements
|
|
8
|
+
# @describe: 判断程序是否每次会更新依赖库,如有更新,则自动安装
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import chardet
|
|
12
|
+
from common.setting import ensure_path_sep
|
|
13
|
+
from utils.logging_tool.log_control import INFO
|
|
14
|
+
from utils import config
|
|
15
|
+
|
|
16
|
+
os.system("pip3 install chardet")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class InstallRequirements:
|
|
20
|
+
""" 自动识别安装最新的依赖库 """
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.version_library_comparisons_path = ensure_path_sep("\\utils\\other_tools\\install_tool\\") \
|
|
24
|
+
+ "version_library_comparisons.txt"
|
|
25
|
+
self.requirements_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) \
|
|
26
|
+
+ os.sep + "requirements.txt"
|
|
27
|
+
|
|
28
|
+
self.mirror_url = config.mirror_source
|
|
29
|
+
# 初始化时,获取最新的版本库
|
|
30
|
+
|
|
31
|
+
# os.system("pip freeze > {0}".format(self.requirements_path))
|
|
32
|
+
|
|
33
|
+
def read_version_library_comparisons_txt(self):
|
|
34
|
+
"""
|
|
35
|
+
获取版本比对默认的文件
|
|
36
|
+
@return:
|
|
37
|
+
"""
|
|
38
|
+
with open(self.version_library_comparisons_path, 'r', encoding="utf-8") as file:
|
|
39
|
+
return file.read().strip(' ')
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def check_charset(cls, file_path):
|
|
43
|
+
"""获取文件的字符集"""
|
|
44
|
+
with open(file_path, "rb") as file:
|
|
45
|
+
data = file.read(4)
|
|
46
|
+
charset = chardet.detect(data)['encoding']
|
|
47
|
+
return charset
|
|
48
|
+
|
|
49
|
+
def read_requirements(self):
|
|
50
|
+
"""获取安装文件"""
|
|
51
|
+
file_data = ""
|
|
52
|
+
with open(
|
|
53
|
+
self.requirements_path,
|
|
54
|
+
'r',
|
|
55
|
+
encoding=self.check_charset(self.requirements_path)
|
|
56
|
+
) as file:
|
|
57
|
+
|
|
58
|
+
for line in file:
|
|
59
|
+
if "[0m" in line:
|
|
60
|
+
line = line.replace("[0m", "")
|
|
61
|
+
file_data += line
|
|
62
|
+
|
|
63
|
+
with open(
|
|
64
|
+
self.requirements_path,
|
|
65
|
+
"w",
|
|
66
|
+
encoding=self.check_charset(self.requirements_path)
|
|
67
|
+
) as file:
|
|
68
|
+
file.write(file_data)
|
|
69
|
+
|
|
70
|
+
return file_data
|
|
71
|
+
|
|
72
|
+
def text_comparison(self):
|
|
73
|
+
"""
|
|
74
|
+
版本库比对
|
|
75
|
+
@return:
|
|
76
|
+
"""
|
|
77
|
+
read_version_library_comparisons_txt = self.read_version_library_comparisons_txt()
|
|
78
|
+
read_requirements = self.read_requirements()
|
|
79
|
+
if read_version_library_comparisons_txt == read_requirements:
|
|
80
|
+
INFO.logger.info("程序中未检查到更新版本库,已为您跳过自动安装库")
|
|
81
|
+
# 程序中如出现不同的文件,则安装
|
|
82
|
+
else:
|
|
83
|
+
INFO.logger.info("程序中检测到您更新了依赖库,已为您自动安装")
|
|
84
|
+
os.system(f"pip3 install -r {self.requirements_path}")
|
|
85
|
+
with open(self.version_library_comparisons_path, "w",
|
|
86
|
+
encoding=self.check_charset(self.requirements_path)) as file:
|
|
87
|
+
file.write(read_requirements)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == '__main__':
|
|
91
|
+
InstallRequirements().text_comparison()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
# @Time : 2022/5/23 18:27
|
|
5
|
+
# @Author : 余少琪
|
|
6
|
+
# @Email : 1603453211@qq.com
|
|
7
|
+
# @File : jsonpath_date_replace
|
|
8
|
+
# @describe:
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def jsonpath_replace(change_data, key_name, data_switch=None):
|
|
13
|
+
"""处理jsonpath数据"""
|
|
14
|
+
_new_data = key_name + ''
|
|
15
|
+
for i in change_data:
|
|
16
|
+
if i == '$':
|
|
17
|
+
pass
|
|
18
|
+
elif data_switch is None and i == "data":
|
|
19
|
+
_new_data += '.data'
|
|
20
|
+
elif i[0] == '[' and i[-1] == ']':
|
|
21
|
+
_new_data += "[" + i[1:-1] + "]"
|
|
22
|
+
else:
|
|
23
|
+
_new_data += '[' + '"' + i + '"' + "]"
|
|
24
|
+
return _new_data
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == '__main__':
|
|
28
|
+
jsonpath_replace(change_data=['$', 'data', 'id'], key_name='self.__yaml_case')
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import types
|
|
2
|
+
from enum import Enum, unique
|
|
3
|
+
from typing import Text, Dict, Callable, Union, Optional, List, Any
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NotificationType(Enum):
|
|
9
|
+
""" 自动化通知方式 """
|
|
10
|
+
DEFAULT = '0'
|
|
11
|
+
DING_TALK = '1'
|
|
12
|
+
WECHAT = '2'
|
|
13
|
+
EMAIL = '3'
|
|
14
|
+
FEI_SHU = '4'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class TestMetrics:
|
|
19
|
+
""" 用例执行数据 """
|
|
20
|
+
passed: int
|
|
21
|
+
failed: int
|
|
22
|
+
broken: int
|
|
23
|
+
skipped: int
|
|
24
|
+
total: int
|
|
25
|
+
pass_rate: float
|
|
26
|
+
time: Text
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RequestType(Enum):
|
|
30
|
+
"""
|
|
31
|
+
request请求发送,请求参数的数据类型
|
|
32
|
+
"""
|
|
33
|
+
JSON = "JSON"
|
|
34
|
+
PARAMS = "PARAMS"
|
|
35
|
+
DATA = "DATA"
|
|
36
|
+
FILE = 'FILE'
|
|
37
|
+
EXPORT = "EXPORT"
|
|
38
|
+
NONE = "NONE"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestCaseEnum(Enum):
|
|
42
|
+
URL = ("url", True)
|
|
43
|
+
HOST = ("host", True)
|
|
44
|
+
METHOD = ("method", True)
|
|
45
|
+
DETAIL = ("detail", True)
|
|
46
|
+
IS_RUN = ("is_run", True)
|
|
47
|
+
HEADERS = ("headers", True)
|
|
48
|
+
REQUEST_TYPE = ("requestType", True)
|
|
49
|
+
DATA = ("data", True)
|
|
50
|
+
DE_CASE = ("dependence_case", True)
|
|
51
|
+
DE_CASE_DATA = ("dependence_case_data", False)
|
|
52
|
+
CURRENT_RE_SET_CACHE = ("current_request_set_cache", False)
|
|
53
|
+
SQL = ("sql", False)
|
|
54
|
+
ASSERT_DATA = ("assert", True)
|
|
55
|
+
SETUP_SQL = ("setup_sql", False)
|
|
56
|
+
TEARDOWN = ("teardown", False)
|
|
57
|
+
TEARDOWN_SQL = ("teardown_sql", False)
|
|
58
|
+
SLEEP = ("sleep", False)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Method(Enum):
|
|
62
|
+
GET = "GET"
|
|
63
|
+
POST = "POST"
|
|
64
|
+
PUT = "PUT"
|
|
65
|
+
PATCH = "PATCH"
|
|
66
|
+
DELETE = "DELETE"
|
|
67
|
+
HEAD = "HEAD"
|
|
68
|
+
OPTION = "OPTION"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def load_module_functions(module) -> Dict[Text, Callable]:
|
|
72
|
+
""" 获取 module中方法的名称和所在的内存地址 """
|
|
73
|
+
module_functions = {}
|
|
74
|
+
|
|
75
|
+
for name, item in vars(module).items():
|
|
76
|
+
if isinstance(item, types.FunctionType):
|
|
77
|
+
module_functions[name] = item
|
|
78
|
+
return module_functions
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@unique
|
|
82
|
+
class DependentType(Enum):
|
|
83
|
+
"""
|
|
84
|
+
数据依赖相关枚举
|
|
85
|
+
"""
|
|
86
|
+
RESPONSE = 'response'
|
|
87
|
+
REQUEST = 'request'
|
|
88
|
+
SQL_DATA = 'sqlData'
|
|
89
|
+
CACHE = "cache"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Assert(BaseModel):
|
|
93
|
+
jsonpath: Text
|
|
94
|
+
type: Text
|
|
95
|
+
value: Any
|
|
96
|
+
AssertType: Union[None, Text] = None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DependentData(BaseModel):
|
|
100
|
+
dependent_type: Text
|
|
101
|
+
jsonpath: Text
|
|
102
|
+
set_cache: Optional[Text] = None
|
|
103
|
+
replace_key: Optional[Text] = None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DependentCaseData(BaseModel):
|
|
107
|
+
case_id: Text
|
|
108
|
+
# dependent_data: List[DependentData]
|
|
109
|
+
dependent_data: Union[None, List[DependentData]] = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ParamPrepare(BaseModel):
|
|
113
|
+
dependent_type: Text
|
|
114
|
+
jsonpath: Text
|
|
115
|
+
set_cache: Text
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SendRequest(BaseModel):
|
|
119
|
+
dependent_type: Text
|
|
120
|
+
jsonpath: Optional[Text] = None
|
|
121
|
+
cache_data: Optional[Text] = None
|
|
122
|
+
set_cache: Optional[Text] = None
|
|
123
|
+
replace_key: Optional[Text] = None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TearDown(BaseModel):
|
|
127
|
+
case_id: Text
|
|
128
|
+
param_prepare: Optional[List["ParamPrepare"]] = None
|
|
129
|
+
send_request: Optional[List["SendRequest"]] = None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class CurrentRequestSetCache(BaseModel):
|
|
133
|
+
type: Text
|
|
134
|
+
jsonpath: Text
|
|
135
|
+
name: Text
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestCase(BaseModel):
|
|
139
|
+
url: Text
|
|
140
|
+
method: Text
|
|
141
|
+
detail: Text
|
|
142
|
+
# assert_data: Union[Dict, Text] = Field(..., alias="assert")
|
|
143
|
+
assert_data: Union[Dict, Text]
|
|
144
|
+
headers: Union[None, Dict, Text] = {}
|
|
145
|
+
requestType: Text
|
|
146
|
+
is_run: Union[None, bool, Text] = None
|
|
147
|
+
data: Any = None
|
|
148
|
+
dependence_case: Union[None, bool] = False
|
|
149
|
+
dependence_case_data: Optional[Union[None, List["DependentCaseData"], Text]] = None
|
|
150
|
+
sql: Optional[List] = None
|
|
151
|
+
setup_sql: Optional[List] = None
|
|
152
|
+
status_code: Optional[int] = None
|
|
153
|
+
teardown_sql: Optional[List] = None
|
|
154
|
+
teardown: Union[List["TearDown"], None] = None
|
|
155
|
+
current_request_set_cache: Optional[List["CurrentRequestSetCache"]] = None
|
|
156
|
+
sleep: Optional[Union[int, float]] = None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ResponseData(BaseModel):
|
|
160
|
+
url: Text
|
|
161
|
+
is_run: Union[None, bool, Text]
|
|
162
|
+
detail: Text
|
|
163
|
+
response_data: Text
|
|
164
|
+
request_body: Any
|
|
165
|
+
method: Text
|
|
166
|
+
sql_data: Dict
|
|
167
|
+
yaml_data: "TestCase"
|
|
168
|
+
headers: Dict
|
|
169
|
+
cookie: Dict
|
|
170
|
+
assert_data: Dict
|
|
171
|
+
res_time: Union[int, float]
|
|
172
|
+
status_code: int
|
|
173
|
+
teardown: Optional[List["TearDown"]] = None
|
|
174
|
+
teardown_sql: Union[None, List] = None
|
|
175
|
+
body: Any
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class DingTalk(BaseModel):
|
|
179
|
+
webhook: Union[Text, None]
|
|
180
|
+
secret: Union[Text, None]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class MySqlDB(BaseModel):
|
|
184
|
+
switch: bool = False
|
|
185
|
+
host: Union[Text, None] = None
|
|
186
|
+
user: Union[Text, None] = None
|
|
187
|
+
password: Union[Text, None] = None
|
|
188
|
+
port: Union[int, None] = 3306
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class Webhook(BaseModel):
|
|
192
|
+
webhook: Union[Text, None]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class Email(BaseModel):
|
|
196
|
+
send_user: Union[Text, None]
|
|
197
|
+
email_host: Union[Text, None]
|
|
198
|
+
stamp_key: Union[Text, None]
|
|
199
|
+
# 收件人
|
|
200
|
+
send_list: Union[Text, None]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class Config(BaseModel):
|
|
204
|
+
project_name: Text
|
|
205
|
+
env: Text
|
|
206
|
+
tester_name: Text
|
|
207
|
+
# Compatible with config values written as either 0 or "0"
|
|
208
|
+
notification_type: Union[Text, int] = '0'
|
|
209
|
+
excel_report: bool
|
|
210
|
+
ding_talk: "DingTalk"
|
|
211
|
+
mysql_db: "MySqlDB"
|
|
212
|
+
mirror_source: Text
|
|
213
|
+
wechat: "Webhook"
|
|
214
|
+
email: "Email"
|
|
215
|
+
lark: "Webhook"
|
|
216
|
+
real_time_update_test_cases: bool = False
|
|
217
|
+
host: Text
|
|
218
|
+
app_host: Union[Text, None]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@unique
|
|
222
|
+
class AllureAttachmentType(Enum):
|
|
223
|
+
"""
|
|
224
|
+
allure 报告的文件类型枚举
|
|
225
|
+
"""
|
|
226
|
+
TEXT = "txt"
|
|
227
|
+
CSV = "csv"
|
|
228
|
+
TSV = "tsv"
|
|
229
|
+
URI_LIST = "uri"
|
|
230
|
+
|
|
231
|
+
HTML = "html"
|
|
232
|
+
XML = "xml"
|
|
233
|
+
JSON = "json"
|
|
234
|
+
YAML = "yaml"
|
|
235
|
+
PCAP = "pcap"
|
|
236
|
+
|
|
237
|
+
PNG = "png"
|
|
238
|
+
JPG = "jpg"
|
|
239
|
+
SVG = "svg"
|
|
240
|
+
GIF = "gif"
|
|
241
|
+
BMP = "bmp"
|
|
242
|
+
TIFF = "tiff"
|
|
243
|
+
|
|
244
|
+
MP4 = "mp4"
|
|
245
|
+
OGG = "ogg"
|
|
246
|
+
WEBM = "webm"
|
|
247
|
+
|
|
248
|
+
PDF = "pdf"
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@unique
|
|
252
|
+
class AssertMethod(Enum):
|
|
253
|
+
"""断言类型"""
|
|
254
|
+
equals = "=="
|
|
255
|
+
less_than = "lt"
|
|
256
|
+
less_than_or_equals = "le"
|
|
257
|
+
greater_than = "gt"
|
|
258
|
+
greater_than_or_equals = "ge"
|
|
259
|
+
not_equals = "not_eq"
|
|
260
|
+
string_equals = "str_eq"
|
|
261
|
+
length_equals = "len_eq"
|
|
262
|
+
length_greater_than = "len_gt"
|
|
263
|
+
length_greater_than_or_equals = 'len_ge'
|
|
264
|
+
length_less_than = "len_lt"
|
|
265
|
+
length_less_than_or_equals = 'len_le'
|
|
266
|
+
contains = "contains"
|
|
267
|
+
contained_by = 'contained_by'
|
|
268
|
+
startswith = 'startswith'
|
|
269
|
+
endswith = 'endswith'
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
# @Time : 2022/3/28 10:52
|
|
5
|
+
# @Author : 余少琪
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PyTimer:
|
|
14
|
+
"""定时器类"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, func, *args, **kwargs):
|
|
17
|
+
"""构造函数"""
|
|
18
|
+
|
|
19
|
+
self.func = func
|
|
20
|
+
self.args = args
|
|
21
|
+
self.kwargs = kwargs
|
|
22
|
+
self.running = False
|
|
23
|
+
|
|
24
|
+
def _run_func(self):
|
|
25
|
+
"""运行定时事件函数"""
|
|
26
|
+
|
|
27
|
+
_thread = threading.Thread(target=self.func, args=self.args, kwargs=self.kwargs)
|
|
28
|
+
_thread.setDaemon(True)
|
|
29
|
+
_thread.start()
|
|
30
|
+
|
|
31
|
+
def _start(self, interval, once):
|
|
32
|
+
"""启动定时器的线程函数"""
|
|
33
|
+
|
|
34
|
+
interval = max(interval, 0.01)
|
|
35
|
+
|
|
36
|
+
if interval < 0.050:
|
|
37
|
+
_dt = interval / 10
|
|
38
|
+
else:
|
|
39
|
+
_dt = 0.005
|
|
40
|
+
|
|
41
|
+
if once:
|
|
42
|
+
deadline = time.time() + interval
|
|
43
|
+
while time.time() < deadline:
|
|
44
|
+
time.sleep(_dt)
|
|
45
|
+
|
|
46
|
+
# 定时时间到,调用定时事件函数
|
|
47
|
+
self._run_func()
|
|
48
|
+
else:
|
|
49
|
+
self.running = True
|
|
50
|
+
deadline = time.time() + interval
|
|
51
|
+
while self.running:
|
|
52
|
+
while time.time() < deadline:
|
|
53
|
+
time.sleep(_dt)
|
|
54
|
+
|
|
55
|
+
# 更新下一次定时时间
|
|
56
|
+
deadline += interval
|
|
57
|
+
|
|
58
|
+
# 定时时间到,调用定时事件函数
|
|
59
|
+
if self.running:
|
|
60
|
+
self._run_func()
|
|
61
|
+
|
|
62
|
+
def start(self, interval, once=False):
|
|
63
|
+
"""启动定时器
|
|
64
|
+
|
|
65
|
+
interval - 定时间隔,浮点型,以秒为单位,最高精度10毫秒
|
|
66
|
+
once - 是否仅启动一次,默认是连续的
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
thread_ = threading.Thread(target=self._start, args=(interval, once))
|
|
70
|
+
thread_.setDaemon(True)
|
|
71
|
+
thread_.start()
|
|
72
|
+
|
|
73
|
+
def stop(self):
|
|
74
|
+
"""停止定时器"""
|
|
75
|
+
|
|
76
|
+
self.running = False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def do_something(name, gender='male'):
|
|
80
|
+
"""执行"""
|
|
81
|
+
print(time.time(), '定时时间到,执行特定任务')
|
|
82
|
+
print('name:%s, gender:%s', name, gender)
|
|
83
|
+
time.sleep(5)
|
|
84
|
+
print(time.time(), '完成特定任务')
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
timer = PyTimer(do_something, 'Alice', gender='female')
|
|
88
|
+
timer.start(0.5, once=False)
|
|
89
|
+
|
|
90
|
+
input('按回车键结束\n') # 此处阻塞住进程
|
|
91
|
+
timer.stop()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
# @Time : 2022/3/28 13:22
|
|
5
|
+
# @Author : 浣欏皯鐞?
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Text
|
|
10
|
+
|
|
11
|
+
from common.setting import data_dir_path, test_case_dir_path, resolve_project_path
|
|
12
|
+
from utils.read_files_tools.get_all_files_path import get_all_files
|
|
13
|
+
from utils.read_files_tools.testcase_template import write_testcase_file
|
|
14
|
+
from utils.read_files_tools.yaml_control import GetYamlData
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestCaseAutomaticGeneration:
|
|
18
|
+
def __init__(self, *, data_dir: Text = None, test_dir: Text = None, force_write: bool = False):
|
|
19
|
+
self.yaml_case_data = None
|
|
20
|
+
self.file_path = None
|
|
21
|
+
self._data_dir = resolve_project_path(data_dir) if data_dir else data_dir_path()
|
|
22
|
+
self._test_dir = resolve_project_path(test_dir) if test_dir else test_case_dir_path()
|
|
23
|
+
self._force_write = force_write
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def case_date_path(self) -> Text:
|
|
27
|
+
"""Return yaml testcase directory path."""
|
|
28
|
+
return self._data_dir
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def case_path(self) -> Text:
|
|
32
|
+
"""Return generated pytest testcase directory path."""
|
|
33
|
+
return self._test_dir
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def allure_epic(self):
|
|
37
|
+
_allure_epic = self.yaml_case_data.get("case_common").get("allureEpic")
|
|
38
|
+
assert _allure_epic is not None, (
|
|
39
|
+
"鐢ㄤ緥涓?allureEpic 涓哄繀濉」锛岃妫€鏌ョ敤渚嬪唴瀹? 鐢ㄤ緥璺緞锛?%s'" % self.file_path
|
|
40
|
+
)
|
|
41
|
+
return _allure_epic
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def allure_feature(self):
|
|
45
|
+
_allure_feature = self.yaml_case_data.get("case_common").get("allureFeature")
|
|
46
|
+
assert _allure_feature is not None, (
|
|
47
|
+
"鐢ㄤ緥涓?allureFeature 涓哄繀濉」锛岃妫€鏌ョ敤渚嬪唴瀹? 鐢ㄤ緥璺緞锛?%s'" % self.file_path
|
|
48
|
+
)
|
|
49
|
+
return _allure_feature
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def allure_story(self):
|
|
53
|
+
_allure_story = self.yaml_case_data.get("case_common").get("allureStory")
|
|
54
|
+
assert _allure_story is not None, (
|
|
55
|
+
"鐢ㄤ緥涓?allureStory 涓哄繀濉」锛岃妫€鏌ョ敤渚嬪唴瀹? 鐢ㄤ緥璺緞锛?%s'" % self.file_path
|
|
56
|
+
)
|
|
57
|
+
return _allure_story
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def file_name(self) -> Text:
|
|
61
|
+
"""
|
|
62
|
+
Convert current yaml file path to relative python path.
|
|
63
|
+
Example: Collect/collect_tool_list.yaml -> Collect/collect_tool_list.py
|
|
64
|
+
"""
|
|
65
|
+
relative = os.path.relpath(self.file_path, self.case_date_path)
|
|
66
|
+
filename, ext = os.path.splitext(relative)
|
|
67
|
+
if ext.lower() not in {".yaml", ".yml"}:
|
|
68
|
+
return relative
|
|
69
|
+
return filename + ".py"
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def get_test_class_title(self):
|
|
73
|
+
"""Generate class name from case filename."""
|
|
74
|
+
_file_name = os.path.split(self.file_name)[1][:-3]
|
|
75
|
+
_name = _file_name.split("_")
|
|
76
|
+
for index in range(len(_name)):
|
|
77
|
+
_name[index] = _name[index].capitalize()
|
|
78
|
+
return "".join(_name)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def func_title(self) -> Text:
|
|
82
|
+
"""Generate test function name from case filename."""
|
|
83
|
+
return os.path.split(self.file_name)[1][:-3]
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def spilt_path(self):
|
|
87
|
+
path = self.file_name.split(os.sep)
|
|
88
|
+
path[-1] = "test_" + path[-1]
|
|
89
|
+
return path
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def get_case_path(self):
|
|
93
|
+
"""Return generated target testcase file absolute path."""
|
|
94
|
+
return os.path.join(self.case_path, *self.spilt_path)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def case_ids(self):
|
|
98
|
+
return [key for key in self.yaml_case_data.keys() if key != "case_common"]
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def get_file_name(self):
|
|
102
|
+
return self.spilt_path[-1]
|
|
103
|
+
|
|
104
|
+
def mk_dir(self) -> None:
|
|
105
|
+
"""Create target test directory if missing."""
|
|
106
|
+
case_dir_path = os.path.split(self.get_case_path)[0]
|
|
107
|
+
if not os.path.exists(case_dir_path):
|
|
108
|
+
os.makedirs(case_dir_path)
|
|
109
|
+
|
|
110
|
+
def get_case_automatic(self) -> None:
|
|
111
|
+
"""Generate pytest test files from all yaml cases."""
|
|
112
|
+
data_path = Path(self.case_date_path)
|
|
113
|
+
if not data_path.exists():
|
|
114
|
+
raise FileNotFoundError(f"YAML data directory does not exist: {data_path}")
|
|
115
|
+
|
|
116
|
+
file_paths = get_all_files(file_path=str(data_path), yaml_data_switch=True)
|
|
117
|
+
for file in file_paths:
|
|
118
|
+
if "proxy_data.yaml" in file:
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
self.yaml_case_data = GetYamlData(file).get_yaml_data()
|
|
122
|
+
self.file_path = file
|
|
123
|
+
self.mk_dir()
|
|
124
|
+
write_testcase_file(
|
|
125
|
+
allure_epic=self.allure_epic,
|
|
126
|
+
allure_feature=self.allure_feature,
|
|
127
|
+
class_title=self.get_test_class_title,
|
|
128
|
+
func_title=self.func_title,
|
|
129
|
+
case_path=self.get_case_path,
|
|
130
|
+
case_ids=self.case_ids,
|
|
131
|
+
file_name=self.get_file_name,
|
|
132
|
+
allure_story=self.allure_story,
|
|
133
|
+
force_write=self._force_write,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
TestCaseAutomaticGeneration().get_case_automatic()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
# @Time : 2022/4/7 11:56
|
|
5
|
+
# @Author : 余少琪
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def del_file(path):
|
|
12
|
+
"""删除目录下的文件"""
|
|
13
|
+
list_path = os.listdir(path)
|
|
14
|
+
for i in list_path:
|
|
15
|
+
c_path = os.path.join(path, i)
|
|
16
|
+
if os.path.isdir(c_path):
|
|
17
|
+
del_file(c_path)
|
|
18
|
+
else:
|
|
19
|
+
os.remove(c_path)
|