pytest-api-framework-alpha 0.2.2__tar.gz → 0.2.3__tar.gz
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.
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/PKG-INFO +1 -1
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/allure_report.py +1 -1
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/base_class.py +5 -6
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/conftest.py +25 -4
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/db/redis_db.py +40 -23
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/global_attribute.py +20 -3
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/http_client.py +16 -14
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/render_data.py +21 -21
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/report.py +1 -1
- pytest_api_framework_alpha-0.2.3/framework/settings.py +30 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/startapp.py +2 -1
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/common.py +23 -14
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/validate.py +84 -19
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/pytest_api_framework_alpha.egg-info/PKG-INFO +1 -1
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/pytest_api_framework_alpha.egg-info/SOURCES.txt +1 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/setup.py +1 -1
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/__init__.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/db/__init__.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/db/mysql_db.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/exit_code.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/extract.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/__init__.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/encrypt.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/log_util.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/teams_util.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/yaml_util.py +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/pytest_api_framework_alpha.egg-info/dependency_links.txt +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/pytest_api_framework_alpha.egg-info/requires.txt +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/pytest_api_framework_alpha.egg-info/top_level.txt +0 -0
- {pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/setup.cfg +0 -0
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/allure_report.py
RENAMED
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
import platform
|
|
4
4
|
from framework.utils.log_util import logger
|
|
5
5
|
from framework.global_attribute import CONTEXT
|
|
6
|
-
from
|
|
6
|
+
from framework.settings import ALLURE_DIR, ALLURE_REPORT_DIR, ALLURE_RESULTS_DIR, ALLURE_ENV_PROPERTIES
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def generate_report():
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/base_class.py
RENAMED
|
@@ -5,16 +5,16 @@ from urllib.parse import urlparse, urlunparse, urljoin
|
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
from box import Box
|
|
8
|
-
from faker import Faker
|
|
9
8
|
|
|
10
9
|
from framework.exit_code import ExitCode
|
|
11
10
|
from framework.db.redis_db import RedisDB
|
|
11
|
+
from framework.db.mysql_db import MysqlDB
|
|
12
12
|
from framework.utils.log_util import logger
|
|
13
13
|
from framework.render_data import RenderData
|
|
14
14
|
from framework.http_client import ResponseUtil
|
|
15
|
+
from framework.settings import UNAUTHORIZED_CODE, FAKER_LANGUAGE
|
|
15
16
|
from framework.utils.common import snake_to_pascal, SingletonFaker
|
|
16
17
|
from framework.global_attribute import CONFIG, GlobalAttribute, _FRAMEWORK_CONTEXT
|
|
17
|
-
from config.settings import UNAUTHORIZED_CODE, FAKER_LANGUAGE
|
|
18
18
|
|
|
19
19
|
module = importlib.import_module("test_case.conftest")
|
|
20
20
|
|
|
@@ -36,7 +36,6 @@ class BaseTestCase(object):
|
|
|
36
36
|
app_http = getattr(self.http, app)
|
|
37
37
|
domain = self.context.get(app).get("domain")
|
|
38
38
|
data = RenderData(data).render()
|
|
39
|
-
url = data.request.get("url")
|
|
40
39
|
data.request.url = self.replace_domain(data.request.url, domain)
|
|
41
40
|
self.response = getattr(app_http, account).request(data=data, kwargs=kwargs)
|
|
42
41
|
if self.response.status_code in UNAUTHORIZED_CODE:
|
|
@@ -76,16 +75,16 @@ class BaseTestCase(object):
|
|
|
76
75
|
request.update({"method": "delete", "headers": {}, **kwargs})
|
|
77
76
|
return self.request(app=app, account=account, data=Box({"request": request}))
|
|
78
77
|
|
|
79
|
-
def mysql_conn(self, db, app=None):
|
|
78
|
+
def mysql_conn(self, db, app=None) -> MysqlDB:
|
|
80
79
|
try:
|
|
81
80
|
return _FRAMEWORK_CONTEXT.get(app=self.default_app(app), key="mysql").get(db)
|
|
82
81
|
except AttributeError as e:
|
|
83
82
|
traceback.print_exc()
|
|
84
83
|
pytest.exit(ExitCode.LOAD_DATABASE_INFO_ERROR)
|
|
85
84
|
|
|
86
|
-
def redis_conn(self, db, app=None):
|
|
85
|
+
def redis_conn(self, db, index=0, app=None) -> RedisDB:
|
|
87
86
|
try:
|
|
88
|
-
return
|
|
87
|
+
return _FRAMEWORK_CONTEXT.get(app=self.default_app(app), key="redis").get(db)[index]
|
|
89
88
|
except AttributeError as e:
|
|
90
89
|
traceback.print_exc()
|
|
91
90
|
pytest.exit(ExitCode.LOAD_DATABASE_INFO_ERROR)
|
|
@@ -22,12 +22,12 @@ from framework.exit_code import ExitCode
|
|
|
22
22
|
from framework.db.mysql_db import MysqlDB
|
|
23
23
|
from framework.db.redis_db import RedisDB
|
|
24
24
|
from framework.utils.log_util import logger
|
|
25
|
-
from framework.render_data import RenderData
|
|
25
|
+
# from framework.render_data import RenderData
|
|
26
26
|
from framework.utils.yaml_util import YamlUtil
|
|
27
|
+
from framework.settings import DATA_DIR, CASES_DIR
|
|
27
28
|
from framework.allure_report import generate_report
|
|
28
29
|
from framework.global_attribute import CONTEXT, CONFIG, _FRAMEWORK_CONTEXT
|
|
29
30
|
from framework.utils.common import snake_to_pascal, get_apps, convert_numbers_to_decimal
|
|
30
|
-
from config.settings import DATA_DIR, CASES_DIR
|
|
31
31
|
|
|
32
32
|
all_app = get_apps()
|
|
33
33
|
module = importlib.import_module("test_case.conftest")
|
|
@@ -139,6 +139,12 @@ def pytest_generate_tests(metafunc):
|
|
|
139
139
|
if not case_common.get("url"):
|
|
140
140
|
logger.warning(f"{func_file_path} request中缺少必填字段: url", case_data)
|
|
141
141
|
# pytest.exit(ExitCode.YAML_MISSING_FIELDS)
|
|
142
|
+
else:
|
|
143
|
+
url = case_common.get("url")
|
|
144
|
+
if url.strip().startswith("${"):
|
|
145
|
+
case_data["request"]["url"] = url
|
|
146
|
+
else:
|
|
147
|
+
case_data["request"]["url"] = urljoin(domain, url)
|
|
142
148
|
else:
|
|
143
149
|
if url.strip().startswith("${"):
|
|
144
150
|
case_data["request"]["url"] = url
|
|
@@ -150,6 +156,8 @@ def pytest_generate_tests(metafunc):
|
|
|
150
156
|
if not case_common.get("method"):
|
|
151
157
|
logger.warning(f"{func_file_path} request中缺少必填字段: method", case_data)
|
|
152
158
|
# pytest.exit(ExitCode.YAML_MISSING_FIELDS)
|
|
159
|
+
else:
|
|
160
|
+
case_data["request"]["method"] = case_common.get("method")
|
|
153
161
|
|
|
154
162
|
for key in ["title", "level"]:
|
|
155
163
|
if key not in case_data:
|
|
@@ -179,8 +187,9 @@ def pytest_generate_tests(metafunc):
|
|
|
179
187
|
# 剔除标记disable的字段
|
|
180
188
|
deep_copied_case_data = disable_field(scenario.get("data"), deep_copied_case_data)
|
|
181
189
|
deep_copied_case_data["_scenario"] = item.get("scenario")
|
|
190
|
+
deep_copied_case_data["_ignore_failed"] = case_common.get("ignore_failed", False)
|
|
182
191
|
case_data_list.append(deep_copied_case_data)
|
|
183
|
-
ids.append(case_data.get("title")
|
|
192
|
+
ids.append(f'{case_data.get("title")} - {scenario.get("describe", "")}#{index + 1}')
|
|
184
193
|
except KeyError as e:
|
|
185
194
|
logger.error(f"scenario参数化格式不正确:{e}")
|
|
186
195
|
traceback.print_exc()
|
|
@@ -189,6 +198,7 @@ def pytest_generate_tests(metafunc):
|
|
|
189
198
|
else:
|
|
190
199
|
if not case_common.get("ignore"):
|
|
191
200
|
case_data["_scenario"] = {"data": {}}
|
|
201
|
+
case_data["_ignore_failed"] = case_common.get("ignore_failed", False)
|
|
192
202
|
case_data_list = [case_data]
|
|
193
203
|
# 进行参数化生成用例
|
|
194
204
|
metafunc.parametrize("data", case_data_list, ids=[f'{case_data.get("title")}#1'], scope="function")
|
|
@@ -408,9 +418,11 @@ def pytest_runtest_call(item):
|
|
|
408
418
|
:param item:
|
|
409
419
|
:return:
|
|
410
420
|
"""
|
|
421
|
+
origin_data = item.funcargs.get("data")
|
|
411
422
|
func_name = item.originalname
|
|
423
|
+
ignore_failed = origin_data.get("_ignore_failed")
|
|
412
424
|
ignore_error_and_continue = item.config.getini("ignore_error_and_continue")
|
|
413
|
-
if ignore_error_and_continue == "
|
|
425
|
+
if not (ignore_failed is True or ignore_error_and_continue == "true"):
|
|
414
426
|
# setup方法执行失败,则主动标记用例执行失败,不会执行用例
|
|
415
427
|
if item.funcargs.get("setup_success") is False:
|
|
416
428
|
pytest.skip("test_before_scenario execute error")
|
|
@@ -590,6 +602,15 @@ def login():
|
|
|
590
602
|
mysqls = CONFIG.get(app=app, key="mysql")
|
|
591
603
|
mysqls_obj = {item: MysqlDB(**mysqls.get(item)) for item in mysqls.keys()}
|
|
592
604
|
_FRAMEWORK_CONTEXT.set(app=app, key="mysql", value=mysqls_obj)
|
|
605
|
+
redis = CONFIG.get(app=app, key="redis")
|
|
606
|
+
redis_obj = {
|
|
607
|
+
db: [
|
|
608
|
+
RedisDB(**{**db_info, "db": i})
|
|
609
|
+
for i in range(16)
|
|
610
|
+
]
|
|
611
|
+
for db, db_info in redis.items()
|
|
612
|
+
}
|
|
613
|
+
_FRAMEWORK_CONTEXT.set(app=app, key="redis", value=redis_obj)
|
|
593
614
|
|
|
594
615
|
try:
|
|
595
616
|
logger.info("登录账号".center(80, "*"))
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/db/redis_db.py
RENAMED
|
@@ -7,7 +7,7 @@ from framework.utils.log_util import logger
|
|
|
7
7
|
|
|
8
8
|
class RedisDB(Redis):
|
|
9
9
|
|
|
10
|
-
def __init__(self, host, port, password, db, max_connections=
|
|
10
|
+
def __init__(self, host, port, password, db, max_connections=1):
|
|
11
11
|
super(RedisDB, self).__init__()
|
|
12
12
|
self.db = db
|
|
13
13
|
self.max_connections = max_connections
|
|
@@ -56,7 +56,7 @@ class RedisDB(Redis):
|
|
|
56
56
|
|
|
57
57
|
if value:
|
|
58
58
|
if isinstance(value, dict):
|
|
59
|
-
self.
|
|
59
|
+
self.conn.lpush(name, json.dumps(value, ensure_ascii=False))
|
|
60
60
|
elif isinstance(value, list):
|
|
61
61
|
if isinstance(value[0], dict):
|
|
62
62
|
value = [json.dumps(item, ensure_ascii=False) for item in value]
|
|
@@ -102,41 +102,58 @@ class RedisDB(Redis):
|
|
|
102
102
|
def set_ttl(self, name, time):
|
|
103
103
|
res = self.conn.expire(name, time)
|
|
104
104
|
logger.info(f'expire {name} {time} ---> {res}')
|
|
105
|
-
|
|
106
|
-
return res
|
|
107
|
-
else:
|
|
108
|
-
return False
|
|
105
|
+
return res
|
|
109
106
|
|
|
110
107
|
def exists(self, name):
|
|
111
108
|
res = self.conn.exists(name)
|
|
112
109
|
logger.info(f'exists {name} ---> {res}')
|
|
113
|
-
|
|
114
|
-
return True
|
|
115
|
-
else:
|
|
116
|
-
return False
|
|
110
|
+
return res
|
|
117
111
|
|
|
118
112
|
def delete(self, name):
|
|
119
113
|
res = self.conn.delete(name)
|
|
120
114
|
logger.info(f'delete {name} ---> {res}')
|
|
121
|
-
|
|
122
|
-
return True
|
|
123
|
-
else:
|
|
124
|
-
return False
|
|
115
|
+
return res
|
|
125
116
|
|
|
126
|
-
def
|
|
117
|
+
def lpush(self, name, *value):
|
|
127
118
|
"""
|
|
128
|
-
通过list
|
|
119
|
+
通过list实现队列,从左侧推消息
|
|
129
120
|
:param name:
|
|
130
121
|
:param value:
|
|
131
122
|
:return:
|
|
132
123
|
"""
|
|
133
|
-
res = self.conn.lpush(name,
|
|
124
|
+
res = self.conn.lpush(name, *value)
|
|
134
125
|
logger.info(f'lpush {name} ---> {value}')
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
126
|
+
return res
|
|
127
|
+
|
|
128
|
+
def rpush(self, name, *value):
|
|
129
|
+
"""
|
|
130
|
+
通过list实现队列,从右侧推消息
|
|
131
|
+
:param name:
|
|
132
|
+
:param value:
|
|
133
|
+
:return:
|
|
134
|
+
"""
|
|
135
|
+
res = self.conn.rpush(name, *value)
|
|
136
|
+
logger.info(f'rpush {name} ---> {value}')
|
|
137
|
+
return res
|
|
139
138
|
|
|
139
|
+
def lpop(self, queue_name):
|
|
140
|
+
logger.info(f'lpop {queue_name}')
|
|
141
|
+
return self.conn.lpop(queue_name)
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
def rpop(self, queue_name):
|
|
144
|
+
logger.info(f'rpop {queue_name}')
|
|
145
|
+
return self.conn.rpop(queue_name)
|
|
146
|
+
|
|
147
|
+
def lpop_all(self, queue_name):
|
|
148
|
+
"""弹出并返回队列中所有元素(会清空队列)"""
|
|
149
|
+
all_elements = []
|
|
150
|
+
try:
|
|
151
|
+
while True:
|
|
152
|
+
element = self.conn.lpop(queue_name)
|
|
153
|
+
if element is None:
|
|
154
|
+
break # 队列为空时退出循环
|
|
155
|
+
all_elements.append(element)
|
|
156
|
+
return all_elements
|
|
157
|
+
except Exception as e:
|
|
158
|
+
print(f"获取所有元素失败: {e}")
|
|
159
|
+
return []
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/global_attribute.py
RENAMED
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import traceback
|
|
3
|
-
import
|
|
3
|
+
from pathlib import Path
|
|
4
4
|
|
|
5
|
+
import yaml
|
|
5
6
|
import pytest
|
|
6
7
|
from box import Box
|
|
8
|
+
from dotenv import set_key
|
|
7
9
|
from box.exceptions import BoxError
|
|
8
10
|
|
|
9
|
-
from config.settings import ROOT_DIR
|
|
10
11
|
from framework.exit_code import ExitCode
|
|
11
12
|
from framework.utils.log_util import logger
|
|
12
|
-
from framework.
|
|
13
|
+
from framework.settings import ROOT_DIR
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class NoDatesSafeLoader(yaml.SafeLoader):
|
|
16
17
|
pass
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def singleton(cls):
|
|
21
|
+
"""
|
|
22
|
+
单例模式装饰器
|
|
23
|
+
:param cls:
|
|
24
|
+
:return:
|
|
25
|
+
"""
|
|
26
|
+
instances = {}
|
|
27
|
+
|
|
28
|
+
def get_instance(*args, **kwargs):
|
|
29
|
+
if cls not in instances:
|
|
30
|
+
instances[cls] = cls(*args, **kwargs)
|
|
31
|
+
return instances[cls]
|
|
32
|
+
|
|
33
|
+
return get_instance
|
|
34
|
+
|
|
35
|
+
|
|
19
36
|
# 禁用 YAML 中的 timestamp 类型自动转换
|
|
20
37
|
for ch in list(NoDatesSafeLoader.yaml_implicit_resolvers):
|
|
21
38
|
resolvers = NoDatesSafeLoader.yaml_implicit_resolvers[ch]
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/http_client.py
RENAMED
|
@@ -14,7 +14,7 @@ from framework.extract import Extract
|
|
|
14
14
|
from framework.validate import Validate
|
|
15
15
|
from framework.utils.log_util import logger
|
|
16
16
|
from framework.global_attribute import CONTEXT
|
|
17
|
-
from
|
|
17
|
+
from framework.settings import CONSOLE_DETAILED_LOG
|
|
18
18
|
from framework.utils.common import convert_numbers_to_decimal
|
|
19
19
|
|
|
20
20
|
|
|
@@ -113,33 +113,35 @@ class HttpClient(object):
|
|
|
113
113
|
logger.info(f"请求url: {request_obj.get('url')}")
|
|
114
114
|
with allure.step(f"请求method: {request_obj.get('method')}"):
|
|
115
115
|
logger.info(f"请求method: {request_obj.get('method')}")
|
|
116
|
-
with allure.step(f"请求headers: {request_obj.get('headers')}"):
|
|
116
|
+
with allure.step(f"请求headers: {built_json.dumps(request_obj.get('headers'))}"):
|
|
117
117
|
if CONSOLE_DETAILED_LOG:
|
|
118
|
-
|
|
118
|
+
|
|
119
|
+
logger.info(f"请求headers: {built_json.dumps(request_obj.get('headers'))}")
|
|
119
120
|
if request_obj.get('params'):
|
|
120
|
-
with allure.step(f"请求参数params: {request_obj.get('params')}"):
|
|
121
|
-
logger.info(f"请求参数params: {request_obj.get('params')}")
|
|
121
|
+
with allure.step(f"请求参数params: {built_json.dumps(request_obj.get('params'))}"):
|
|
122
|
+
logger.info(f"请求参数params: {built_json.dumps(request_obj.get('params'))}")
|
|
122
123
|
if request_obj.get('data'):
|
|
123
|
-
with allure.step(f"请求参数data: {request_obj.get('data')}"):
|
|
124
|
-
logger.info(f"请求参数data: {request_obj.get('data')}")
|
|
124
|
+
with allure.step(f"请求参数data: {built_json.dumps(request_obj.get('data'))}"):
|
|
125
|
+
logger.info(f"请求参数data: {built_json.dumps(request_obj.get('data'))}")
|
|
125
126
|
if request_obj.get('json'):
|
|
126
|
-
with allure.step(f"请求参数json: {request_obj.get('json')}"):
|
|
127
|
-
logger.info(f"请求参数json: {request_obj.get('json')}")
|
|
127
|
+
with allure.step(f"请求参数json: {built_json.dumps(request_obj.get('json'))}"):
|
|
128
|
+
logger.info(f"请求参数json: {built_json.dumps(request_obj.get('json'))}")
|
|
128
129
|
|
|
129
130
|
with allure.step("响应结果"):
|
|
130
131
|
with allure.step(f"响应status code: {self.response.status_code}"):
|
|
131
132
|
logger.info(f"响应status code: {self.response.status_code}")
|
|
132
|
-
|
|
133
|
+
|
|
134
|
+
with allure.step(f"响应headers: {built_json.dumps(dict(self.response.headers))}"):
|
|
133
135
|
if CONSOLE_DETAILED_LOG:
|
|
134
|
-
logger.info(f"响应headers: {self.response.headers}")
|
|
135
|
-
with allure.step(f"响应body: {self.response.json()}"):
|
|
136
|
-
logger.info(f"响应body: {self.response.json()}")
|
|
136
|
+
logger.info(f"响应headers: {built_json.dumps(dict(self.response.headers))}")
|
|
137
|
+
with allure.step(f"响应body: {built_json.dumps(self.response.json())}"):
|
|
138
|
+
logger.info(f"响应body: {built_json.dumps(self.response.json())}")
|
|
137
139
|
|
|
138
140
|
# 断言
|
|
139
141
|
validates = data.get("validate")
|
|
140
142
|
if validates:
|
|
141
143
|
with allure.step("结果断言"):
|
|
142
|
-
Validate(self.response).valid(validates)
|
|
144
|
+
Validate(data, self.response).valid(validates)
|
|
143
145
|
|
|
144
146
|
# 提取变量
|
|
145
147
|
expressions = data.get("extract")
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/render_data.py
RENAMED
|
@@ -9,8 +9,8 @@ from box import Box
|
|
|
9
9
|
from framework.exit_code import ExitCode
|
|
10
10
|
from framework.utils.log_util import logger
|
|
11
11
|
from framework.global_attribute import CONTEXT
|
|
12
|
-
from framework.
|
|
13
|
-
from
|
|
12
|
+
from framework.settings import FAKER_LANGUAGE, DATA_DIR
|
|
13
|
+
from framework.utils.common import SingletonFaker, remove_spaces
|
|
14
14
|
|
|
15
15
|
module = importlib.import_module("framework.utils.common")
|
|
16
16
|
|
|
@@ -34,11 +34,23 @@ class RenderData(object):
|
|
|
34
34
|
"""
|
|
35
35
|
with allure.step("渲染数据"):
|
|
36
36
|
self.replace_attribute(self.scenario)
|
|
37
|
+
self.render_url()
|
|
37
38
|
self.replace_attribute(self.request)
|
|
38
39
|
self.data["request"] = self.request
|
|
39
40
|
self.data["_is_multipart"] = self._is_multipart
|
|
40
41
|
return self.data
|
|
41
42
|
|
|
43
|
+
def render_url(self):
|
|
44
|
+
url = remove_spaces(self.request.url)
|
|
45
|
+
pattern = re.compile(r"\$\{([\w.\[\]0-9]+(?:\(\w*(?:,\w*)*\))?)}")
|
|
46
|
+
|
|
47
|
+
def replacer(match):
|
|
48
|
+
item = match.group(1) # 拿到占位符里的内容
|
|
49
|
+
value = self.get_attribute(item)
|
|
50
|
+
return str(value) if value is not None else match.group(0) # 未取到就保留原样
|
|
51
|
+
|
|
52
|
+
self.request.url = pattern.sub(replacer, url)
|
|
53
|
+
|
|
42
54
|
def replace_attribute(self, data):
|
|
43
55
|
pattern = re.compile(r"\$\{([\w.\[\]0-9]+(?:\(\w*(?:,\w*)*\))?)}")
|
|
44
56
|
file_path_pattern = re.compile(
|
|
@@ -52,6 +64,7 @@ class RenderData(object):
|
|
|
52
64
|
self.replace_attribute(value)
|
|
53
65
|
# 如果是字符串类型并匹配正则表达式,则替换
|
|
54
66
|
elif isinstance(value, str):
|
|
67
|
+
value = remove_spaces(value)
|
|
55
68
|
if pattern.search(value):
|
|
56
69
|
data[key] = self.get_attribute(value[2:-1])
|
|
57
70
|
elif file_path_pattern.search(value):
|
|
@@ -66,6 +79,7 @@ class RenderData(object):
|
|
|
66
79
|
self.replace_attribute(item)
|
|
67
80
|
# 如果是字符串类型并匹配正则表达式,则替换
|
|
68
81
|
elif isinstance(item, str):
|
|
82
|
+
item = remove_spaces(item)
|
|
69
83
|
if pattern.search(item):
|
|
70
84
|
data[index] = self.get_attribute(item[2:-1])
|
|
71
85
|
elif file_path_pattern.search(item):
|
|
@@ -73,7 +87,7 @@ class RenderData(object):
|
|
|
73
87
|
self._is_multipart = True
|
|
74
88
|
|
|
75
89
|
def get_attribute(self, keyword):
|
|
76
|
-
if not keyword.endswith(")"):
|
|
90
|
+
if not keyword.strip().endswith(")"):
|
|
77
91
|
return self.get_attribute_variable(keyword)
|
|
78
92
|
# 如果关键字是函数
|
|
79
93
|
else:
|
|
@@ -96,7 +110,7 @@ class RenderData(object):
|
|
|
96
110
|
:param expression:
|
|
97
111
|
:return:
|
|
98
112
|
"""
|
|
99
|
-
if not expression.startswith(tuple(f"{app}." for app in self.context.all_app)):
|
|
113
|
+
if not expression.strip().startswith(tuple(f"{app}." for app in self.context.all_app)):
|
|
100
114
|
belong_app = self.data.get('_belong_app')
|
|
101
115
|
new_expression = f"{belong_app}.{expression}"
|
|
102
116
|
else:
|
|
@@ -108,23 +122,9 @@ class RenderData(object):
|
|
|
108
122
|
if self.get_nested_value(self.context, new_expression) is not None else
|
|
109
123
|
self.get_nested_value(self.context, expression)
|
|
110
124
|
)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# pattern = re.compile(r'(?P<func_name>.+)\((?P<args>.*)\)')
|
|
115
|
-
# match = re.match(pattern, keyword)
|
|
116
|
-
# # 匹配方法名
|
|
117
|
-
# func_name = match.group("func_name")
|
|
118
|
-
# # 匹配方法位置参数
|
|
119
|
-
# args = match.group("args")
|
|
120
|
-
# if args:
|
|
121
|
-
# # 将参数中字符串类型的数字转成数字类型
|
|
122
|
-
# args = [eval(i) for i in args.replace(",", "").split(",")]
|
|
123
|
-
# value = self.get_func_variable(keyword, func_name, *args)
|
|
124
|
-
# else:
|
|
125
|
-
# value = self.get_func_variable(keyword, func_name)
|
|
126
|
-
# except Exception:
|
|
127
|
-
# pass
|
|
125
|
+
if isinstance(value, str) and value.strip().startswith("${") and value.strip().endswith("}"):
|
|
126
|
+
value = self.get_nested_value(self.context, new_expression) or self.get_nested_value(self.context,
|
|
127
|
+
expression)
|
|
128
128
|
|
|
129
129
|
with allure.step(f"{expression}: {value}"):
|
|
130
130
|
logger.info(f"前置读取变量: {expression}: {value}")
|
|
@@ -4,7 +4,7 @@ import datetime
|
|
|
4
4
|
import traceback
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from framework.db.mysql_db import MysqlDB
|
|
7
|
-
from
|
|
7
|
+
from framework.settings import DATABASE_HOST, DATABASE_PASSWORD, DATABASE_DB, DATABASE_USERNAME, DATABASE_PORT
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def timestamp_to_datetime_str(timestamp):
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
ROOT_DIR = os.getcwd()
|
|
4
|
+
# 生成allure报告程序的路径
|
|
5
|
+
ALLURE_DIR = os.getenv("ALLURE_DIR")
|
|
6
|
+
# allure统计执行结果目录
|
|
7
|
+
ALLURE_RESULTS_DIR = os.path.join(ROOT_DIR, "allure_results")
|
|
8
|
+
# allure报告目录
|
|
9
|
+
ALLURE_REPORT_DIR = os.path.join(ROOT_DIR, "allure_report")
|
|
10
|
+
# allure报告统计执行环境的存放路径
|
|
11
|
+
ALLURE_ENV_PROPERTIES = os.path.join(ALLURE_RESULTS_DIR, "environment.properties")
|
|
12
|
+
# 存放case的目录
|
|
13
|
+
CASES_DIR = os.path.join(ROOT_DIR, "test_case")
|
|
14
|
+
# 存放测试数据的目录
|
|
15
|
+
DATA_DIR = os.path.join(ROOT_DIR, "test_data")
|
|
16
|
+
# 存放配置的目录
|
|
17
|
+
CONFIG_DIR = os.path.join(ROOT_DIR, "config")
|
|
18
|
+
|
|
19
|
+
FAKER_LANGUAGE = "en_US"
|
|
20
|
+
# 是否记录详细日志
|
|
21
|
+
CONSOLE_DETAILED_LOG = False
|
|
22
|
+
# 鉴权失败返回的http code
|
|
23
|
+
UNAUTHORIZED_CODE = [401]
|
|
24
|
+
# response属性
|
|
25
|
+
RESPONSE_ATTR = ["status_code", "url", "ok", "encoding"]
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
from config.settings import *
|
|
29
|
+
except ImportError:
|
|
30
|
+
pass
|
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
import yaml
|
|
6
6
|
from framework.utils.common import snake_to_pascal
|
|
7
|
-
from
|
|
7
|
+
from framework.settings import CONFIG_DIR, CASES_DIR, DATA_DIR
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def create_yaml(app):
|
|
@@ -68,6 +68,7 @@ def create_test_data(app):
|
|
|
68
68
|
"case_common": {
|
|
69
69
|
"module": "功能模块名称",
|
|
70
70
|
"describe": "测试场景描述",
|
|
71
|
+
"ignore_failed": False, # 默认false,遇到失败case会忽略当前类中后续case,直接执行下个测试类
|
|
71
72
|
"scenarios": [
|
|
72
73
|
{
|
|
73
74
|
"scenario": {
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/common.py
RENAMED
|
@@ -11,8 +11,27 @@ from urllib.parse import unquote, quote, quote_plus, unquote_plus
|
|
|
11
11
|
import pyotp
|
|
12
12
|
import cn2an as c2a
|
|
13
13
|
from faker import Faker
|
|
14
|
+
from framework.settings import CONFIG_DIR
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
try:
|
|
17
|
+
from utils.common import *
|
|
18
|
+
except ImportError:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SingletonFaker(object):
|
|
23
|
+
instance = None
|
|
24
|
+
init_flag = False
|
|
25
|
+
|
|
26
|
+
def __init__(self, locale):
|
|
27
|
+
if self.init_flag:
|
|
28
|
+
return
|
|
29
|
+
self.faker = Faker(locale)
|
|
30
|
+
|
|
31
|
+
def __new__(cls, *args, **kwargs):
|
|
32
|
+
if cls.instance is None:
|
|
33
|
+
cls.instance = super().__new__(cls)
|
|
34
|
+
return cls.instance
|
|
16
35
|
|
|
17
36
|
|
|
18
37
|
def generate_2fa_code(secret_key):
|
|
@@ -243,19 +262,9 @@ def convert_numbers_to_decimal(obj: Any) -> Any:
|
|
|
243
262
|
return obj
|
|
244
263
|
|
|
245
264
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def __init__(self, locale):
|
|
251
|
-
if self.init_flag:
|
|
252
|
-
return
|
|
253
|
-
self.faker = Faker(locale)
|
|
254
|
-
|
|
255
|
-
def __new__(cls, *args, **kwargs):
|
|
256
|
-
if cls.instance is None:
|
|
257
|
-
cls.instance = super().__new__(cls)
|
|
258
|
-
return cls.instance
|
|
265
|
+
def remove_spaces(s: str) -> str:
|
|
266
|
+
"""去掉字符串中的所有空格"""
|
|
267
|
+
return s.replace(" ", "")
|
|
259
268
|
|
|
260
269
|
|
|
261
270
|
if __name__ == '__main__':
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import re
|
|
2
2
|
|
|
3
3
|
import allure
|
|
4
|
+
import pytest
|
|
4
5
|
from box import Box, BoxList
|
|
5
6
|
from jsonpath import jsonpath
|
|
6
7
|
|
|
7
8
|
from framework.utils.log_util import logger
|
|
9
|
+
from framework.render_data import RenderData
|
|
10
|
+
from framework.settings import RESPONSE_ATTR
|
|
8
11
|
from framework.utils.common import an2cn, is_digit
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
class Validate(object):
|
|
12
|
-
def __init__(self, response):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
elif isinstance(response.json(), list):
|
|
16
|
-
self.response = BoxList(response.json())
|
|
17
|
-
else:
|
|
18
|
-
self.response = response
|
|
15
|
+
def __init__(self, data, response):
|
|
16
|
+
self.data = data
|
|
17
|
+
self.response = response
|
|
19
18
|
|
|
20
19
|
self.mapping = {
|
|
21
20
|
"eq": "assert_equal",
|
|
@@ -32,7 +31,9 @@ class Validate(object):
|
|
|
32
31
|
"contains": "assert_contains",
|
|
33
32
|
"contained_by": "assert_contained_by",
|
|
34
33
|
"startswith": "assert_startswith",
|
|
35
|
-
"endswith": "assert_endswith"
|
|
34
|
+
"endswith": "assert_endswith",
|
|
35
|
+
"is_null": "assert_is_null",
|
|
36
|
+
"not_null": "assert_is_not_null",
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
def valid(self, validates):
|
|
@@ -40,23 +41,26 @@ class Validate(object):
|
|
|
40
41
|
key = list(valid_item.keys())[0]
|
|
41
42
|
valid_list = [i.strip() for i in valid_item.get(key).split(",")]
|
|
42
43
|
expression = valid_list[0]
|
|
43
|
-
|
|
44
|
+
try:
|
|
45
|
+
expectant_result = valid_list[1]
|
|
46
|
+
except IndexError:
|
|
47
|
+
expectant_result = ""
|
|
44
48
|
func = self.mapping.get(key)
|
|
45
49
|
try:
|
|
46
50
|
if func:
|
|
47
|
-
getattr(self, func)(expression, expectant_result)
|
|
51
|
+
result = getattr(self, func)(expression, expectant_result.strip())
|
|
48
52
|
with allure.step(
|
|
49
|
-
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果:
|
|
53
|
+
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果: {result}"):
|
|
50
54
|
logger.info(
|
|
51
|
-
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果:
|
|
55
|
+
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果: {result}")
|
|
52
56
|
else:
|
|
53
57
|
logger.error(f"不支持的断言方式: {key}")
|
|
54
58
|
except AssertionError as e:
|
|
55
59
|
with allure.step(
|
|
56
|
-
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果:
|
|
60
|
+
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果: {result}。 失败原因: {e}"):
|
|
57
61
|
logger.error(
|
|
58
|
-
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果:
|
|
59
|
-
|
|
62
|
+
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果: {result}。 失败原因: {e}")
|
|
63
|
+
pytest.fail(e)
|
|
60
64
|
|
|
61
65
|
def parse_expectant_expression(self, expectant_expression):
|
|
62
66
|
|
|
@@ -78,7 +82,7 @@ class Validate(object):
|
|
|
78
82
|
|
|
79
83
|
def exec_jsonpath(self, expression):
|
|
80
84
|
try:
|
|
81
|
-
return jsonpath(self.response, expression)[0]
|
|
85
|
+
return jsonpath(self.response.json(), expression)[0]
|
|
82
86
|
except Exception as e:
|
|
83
87
|
raise Exception(f"jsonpath表达式错误或非预期响应内容{e} 表达式: {expression};响应内容: {self.response}")
|
|
84
88
|
|
|
@@ -94,7 +98,9 @@ class Validate(object):
|
|
|
94
98
|
|
|
95
99
|
def exec_box(self, expression):
|
|
96
100
|
try:
|
|
97
|
-
|
|
101
|
+
if expression in RESPONSE_ATTR:
|
|
102
|
+
return getattr(self.response, expression)
|
|
103
|
+
return self.get_nested_value(Box(self.response.json()), expression)
|
|
98
104
|
except Exception as e:
|
|
99
105
|
raise Exception(f"box表达式或响应内容异常{e} 表达式: {expression}; 响应内容: {self.response.text}")
|
|
100
106
|
|
|
@@ -116,105 +122,164 @@ class Validate(object):
|
|
|
116
122
|
def assert_equal(self, expectant_expression, practical_result):
|
|
117
123
|
expectant_result = self.parse_expectant_expression(expectant_expression)
|
|
118
124
|
if isinstance(expectant_result, (int, float)) and isinstance(practical_result, str):
|
|
119
|
-
practical_result
|
|
125
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith(
|
|
126
|
+
"}"):
|
|
127
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
128
|
+
else:
|
|
129
|
+
practical_result = eval(practical_result)
|
|
120
130
|
assert practical_result == expectant_result, f'{expectant_result} == {practical_result}'
|
|
131
|
+
return f'{expectant_result} == {practical_result}'
|
|
121
132
|
|
|
122
133
|
def assert_not_equal(self, expectant_expression, practical_result):
|
|
123
134
|
expectant_result = self.parse_expectant_expression(expectant_expression)
|
|
124
135
|
if isinstance(expectant_result, (int, float)) and isinstance(practical_result, str):
|
|
125
|
-
practical_result
|
|
136
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith(
|
|
137
|
+
"}"):
|
|
138
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
139
|
+
else:
|
|
140
|
+
practical_result = eval(practical_result)
|
|
126
141
|
assert practical_result != expectant_result, f'{expectant_result} != {practical_result}'
|
|
142
|
+
return f'{expectant_result} != {practical_result}'
|
|
127
143
|
|
|
128
144
|
def assert_less_than(self, expectant_expression, practical_result):
|
|
129
145
|
expectant_result, practical_result = self.__to_digit(
|
|
130
146
|
self.parse_expectant_expression(expectant_expression),
|
|
131
147
|
practical_result
|
|
132
148
|
)
|
|
149
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
150
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
133
151
|
assert expectant_result < practical_result, f'{expectant_result} < {practical_result}'
|
|
152
|
+
return f'{expectant_result} < {practical_result}'
|
|
134
153
|
|
|
135
154
|
def assert_less_than_or_equals(self, expectant_expression, practical_result):
|
|
136
155
|
expectant_result, practical_result = self.__to_digit(
|
|
137
156
|
self.parse_expectant_expression(expectant_expression),
|
|
138
157
|
practical_result
|
|
139
158
|
)
|
|
159
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
160
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
140
161
|
assert expectant_result <= practical_result, f'{expectant_result} <= {practical_result}'
|
|
162
|
+
return f'{expectant_result} <= {practical_result}'
|
|
141
163
|
|
|
142
164
|
def assert_greater_than(self, expectant_expression, practical_result):
|
|
143
165
|
expectant_result, practical_result = self.__to_digit(
|
|
144
166
|
self.parse_expectant_expression(expectant_expression),
|
|
145
167
|
practical_result
|
|
146
168
|
)
|
|
169
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
170
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
147
171
|
assert expectant_result > practical_result, f'{expectant_result} > {practical_result}'
|
|
172
|
+
return f'{expectant_result} > {practical_result}'
|
|
148
173
|
|
|
149
174
|
def assert_greater_than_or_equals(self, expectant_expression, practical_result):
|
|
150
175
|
expectant_result, practical_result = self.__to_digit(
|
|
151
176
|
self.parse_expectant_expression(expectant_expression),
|
|
152
177
|
practical_result
|
|
153
178
|
)
|
|
179
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
180
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
154
181
|
assert expectant_result >= practical_result, f'{expectant_result} >= {practical_result}'
|
|
182
|
+
return f'{expectant_result} >= {practical_result}'
|
|
155
183
|
|
|
156
184
|
def assert_contains(self, expectant_expression, practical_result):
|
|
157
185
|
expectant_result, practical_result = self.__to_str(
|
|
158
186
|
self.parse_expectant_expression(expectant_expression),
|
|
159
187
|
practical_result
|
|
160
188
|
)
|
|
189
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
190
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
161
191
|
assert practical_result in expectant_result, f'{practical_result} in {expectant_result}'
|
|
192
|
+
return f'{practical_result} in {expectant_result}'
|
|
162
193
|
|
|
163
194
|
def assert_contained_by(self, expectant_expression, practical_result):
|
|
164
195
|
expectant_result, practical_result = self.__to_str(
|
|
165
196
|
self.parse_expectant_expression(expectant_expression),
|
|
166
197
|
practical_result
|
|
167
198
|
)
|
|
199
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
200
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
168
201
|
assert expectant_result in practical_result, f'{expectant_result} in {practical_result}'
|
|
202
|
+
return f'{expectant_result} in {practical_result}'
|
|
169
203
|
|
|
170
204
|
def assert_startswith(self, expectant_expression, practical_result):
|
|
171
205
|
expectant_result, practical_result = self.__to_str(
|
|
172
206
|
self.parse_expectant_expression(expectant_expression),
|
|
173
207
|
practical_result
|
|
174
208
|
)
|
|
209
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
210
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
175
211
|
assert expectant_result.startswith(practical_result), f'{expectant_result} 以 {practical_result} 开头'
|
|
212
|
+
return f'{expectant_result} 以 {practical_result} 开头'
|
|
176
213
|
|
|
177
214
|
def assert_endswith(self, expectant_expression, practical_result):
|
|
178
215
|
expectant_result, practical_result = self.__to_str(
|
|
179
216
|
self.parse_expectant_expression(expectant_expression),
|
|
180
217
|
practical_result
|
|
181
218
|
)
|
|
219
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
220
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
182
221
|
assert expectant_result.endswith(practical_result), f'{expectant_result} 以 {practical_result} 结尾'
|
|
222
|
+
return f'{expectant_result} 以 {practical_result} 结尾'
|
|
183
223
|
|
|
184
224
|
def assert_length_equals(self, expectant_expression, practical_result):
|
|
185
225
|
expectant_result, practical_result = self.__to_str(
|
|
186
226
|
self.parse_expectant_expression(expectant_expression),
|
|
187
227
|
practical_result
|
|
188
228
|
)
|
|
229
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
230
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
189
231
|
assert len(expectant_result) == int(practical_result), f'{len(expectant_result)} == {practical_result}'
|
|
232
|
+
return f'{len(expectant_result)} == {practical_result}'
|
|
190
233
|
|
|
191
234
|
def assert_length_less_than(self, expectant_expression, practical_result):
|
|
192
235
|
expectant_result, practical_result = self.__to_str(
|
|
193
236
|
self.parse_expectant_expression(expectant_expression),
|
|
194
237
|
practical_result
|
|
195
238
|
)
|
|
239
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
240
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
196
241
|
assert len(expectant_result) < int(practical_result), f'{len(expectant_result)} < {practical_result}'
|
|
242
|
+
return f'{len(expectant_result)} < {practical_result}'
|
|
197
243
|
|
|
198
244
|
def assert_length_less_than_or_equals(self, expectant_expression, practical_result):
|
|
199
245
|
expectant_result, practical_result = self.__to_str(
|
|
200
246
|
self.parse_expectant_expression(expectant_expression),
|
|
201
247
|
practical_result
|
|
202
248
|
)
|
|
249
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
250
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
203
251
|
assert len(expectant_result) <= int(practical_result), f'{len(expectant_result)} <= {practical_result}'
|
|
252
|
+
return f'{len(expectant_result)} <= {practical_result}'
|
|
204
253
|
|
|
205
254
|
def assert_length_greater_than(self, expectant_expression, practical_result):
|
|
206
255
|
expectant_result, practical_result = self.__to_str(
|
|
207
256
|
self.parse_expectant_expression(expectant_expression),
|
|
208
257
|
practical_result
|
|
209
258
|
)
|
|
259
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
260
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
210
261
|
assert len(expectant_result) > int(practical_result), f'{len(expectant_result)} > {practical_result}'
|
|
262
|
+
return f'{len(expectant_result)} > {practical_result}'
|
|
211
263
|
|
|
212
264
|
def assert_length_greater_than_or_equals(self, expectant_expression, practical_result):
|
|
213
265
|
expectant_result, practical_result = self.__to_str(
|
|
214
266
|
self.parse_expectant_expression(expectant_expression),
|
|
215
267
|
practical_result
|
|
216
268
|
)
|
|
269
|
+
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith("}"):
|
|
270
|
+
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
217
271
|
assert len(expectant_result) >= int(practical_result), f'{len(expectant_result)} >= {practical_result}'
|
|
272
|
+
return f'{len(expectant_result)} >= {practical_result}'
|
|
273
|
+
|
|
274
|
+
def assert_is_null(self, expectant_expression, practical_result):
|
|
275
|
+
expectant_result = self.parse_expectant_expression(expectant_expression)
|
|
276
|
+
assert expectant_result is None
|
|
277
|
+
return expectant_result
|
|
278
|
+
|
|
279
|
+
def assert_is_not_null(self, expectant_expression, practical_result):
|
|
280
|
+
expectant_result = self.parse_expectant_expression(expectant_expression)
|
|
281
|
+
assert expectant_result is not None
|
|
282
|
+
return expectant_result
|
|
218
283
|
|
|
219
284
|
def __to_str(self, expectant_expression, practical_result):
|
|
220
285
|
if isinstance(expectant_expression, (int, float)):
|
|
File without changes
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/db/__init__.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/db/mysql_db.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/exit_code.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/__init__.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/encrypt.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/log_util.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/teams_util.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.2.2 → pytest_api_framework_alpha-0.2.3}/framework/utils/yaml_util.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|