xiaochaotools 2.0.6__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.
- xiaochaotools/__init__.py +5 -0
- xiaochaotools/assertion/__init__.py +71 -0
- xiaochaotools/assertion/custom/__init__.py +44 -0
- xiaochaotools/assertion/file/__init__.py +11 -0
- xiaochaotools/assertion/file/_excel.py +275 -0
- xiaochaotools/assertion/file/_txt.py +110 -0
- xiaochaotools/assertion/sql/__init__.py +10 -0
- xiaochaotools/assertion/sql/_sql.py +300 -0
- xiaochaotools/assertion/text/__init__.py +18 -0
- xiaochaotools/assertion/text/_contain.py +84 -0
- xiaochaotools/assertion/text/_matching.py +146 -0
- xiaochaotools/assertion/text/_whatis.py +75 -0
- xiaochaotools/assertion/text/_whatisit.py +108 -0
- xiaochaotools/dal/__init__.py +49 -0
- xiaochaotools/dal/accessors.py +111 -0
- xiaochaotools/dal/assertions.py +707 -0
- xiaochaotools/dal/bdd_steps.py +329 -0
- xiaochaotools/dal/core/__init__.py +36 -0
- xiaochaotools/dal/core/ast_nodes.py +307 -0
- xiaochaotools/dal/core/lexer.py +431 -0
- xiaochaotools/dal/core/operators.py +660 -0
- xiaochaotools/dal/core/parser.py +910 -0
- xiaochaotools/dal/core/parser_new.py +515 -0
- xiaochaotools/dal/core/parser_old.py +735 -0
- xiaochaotools/dal/core/runtime.py +531 -0
- xiaochaotools/dal/schema.py +414 -0
- xiaochaotools/data_processor/__init__.py +122 -0
- xiaochaotools/data_processor/_cache_tool.py +69 -0
- xiaochaotools/data_processor/_coding_tool.py +25 -0
- xiaochaotools/data_processor/_encryption_tool.py +47 -0
- xiaochaotools/data_processor/_json_tool.py +74 -0
- xiaochaotools/data_processor/_random_character_info_data.py +124 -0
- xiaochaotools/data_processor/_random_number_data.py +246 -0
- xiaochaotools/data_processor/_random_string_data.py +347 -0
- xiaochaotools/data_processor/_random_time_data.py +431 -0
- xiaochaotools/data_processor/_sql_cache.py +152 -0
- xiaochaotools/database/__init__.py +14 -0
- xiaochaotools/database/_async_mysql_connect.py +83 -0
- xiaochaotools/database/_mysql_connect.py +147 -0
- xiaochaotools/database/_mysql_pool.py +376 -0
- xiaochaotools/database/_sqlite_connect.py +41 -0
- xiaochaotools/decorator/__init__.py +21 -0
- xiaochaotools/decorator/convert_args.py +29 -0
- xiaochaotools/decorator/data_method.py +82 -0
- xiaochaotools/decorator/method_callback.py +66 -0
- xiaochaotools/decorator/retry.py +46 -0
- xiaochaotools/decorator/singleton.py +21 -0
- xiaochaotools/enums/__init__.py +13 -0
- xiaochaotools/enums/_base_enum.py +38 -0
- xiaochaotools/enums/_enums.py +57 -0
- xiaochaotools/exceptions/__init__.py +9 -0
- xiaochaotools/exceptions/_exceptions.py +15 -0
- xiaochaotools/exceptions/error_msg.py +26 -0
- xiaochaotools/log_collector/__init__.py +98 -0
- xiaochaotools/log_collector/_log_control.py +98 -0
- xiaochaotools/method.py +95 -0
- xiaochaotools/models/__init__.py +20 -0
- xiaochaotools/models/_models.py +97 -0
- xiaochaotools/monitoring/__init__.py +6 -0
- xiaochaotools/monitoring/base_monitor.py +192 -0
- xiaochaotools/notice/__init__.py +14 -0
- xiaochaotools/notice/_feishu_send.py +264 -0
- xiaochaotools/notice/_mail_send.py +251 -0
- xiaochaotools/notice/_wechat_send.py +166 -0
- xiaochaotools/xiaochaos/__init__.py +14 -0
- xiaochaotools/xiaochaos/core.py +217 -0
- xiaochaotools/xiaochaos/flow.py +127 -0
- xiaochaotools/xiaochaos/git.py +397 -0
- xiaochaotools-2.0.6.dist-info/METADATA +43 -0
- xiaochaotools-2.0.6.dist-info/RECORD +73 -0
- xiaochaotools-2.0.6.dist-info/WHEEL +5 -0
- xiaochaotools-2.0.6.dist-info/licenses/LICENSE +21 -0
- xiaochaotools-2.0.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Project: 筱超测试平台
|
|
3
|
+
# @Description:
|
|
4
|
+
# @Time : 2023/4/6 13:36
|
|
5
|
+
# @Author : 筱超
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
from xiaochaotools.exceptions import XiaochaoToolsError
|
|
11
|
+
from xiaochaotools.exceptions.error_msg import ERROR_MSG_0021, ERROR_MSG_0019
|
|
12
|
+
from .custom import CustomAssertion
|
|
13
|
+
from .file import FileAssertion, ExcelAssertion
|
|
14
|
+
from .sql import SqlAssertion
|
|
15
|
+
from .text import TextAssertion
|
|
16
|
+
from ..data_processor import DataProcessor
|
|
17
|
+
from ..database import MysqlConnect
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class XiaochaoAssertion(CustomAssertion, FileAssertion, SqlAssertion, TextAssertion):
|
|
21
|
+
|
|
22
|
+
def __init__(self, mysql_conn: MysqlConnect | None = None, test_data: DataProcessor | None = None):
|
|
23
|
+
SqlAssertion.__init__(self, mysql_conn)
|
|
24
|
+
CustomAssertion.__init__(self, test_data)
|
|
25
|
+
|
|
26
|
+
def ass(self, method: str, actual, expect=None) -> str:
|
|
27
|
+
if hasattr(TextAssertion, method) and callable(getattr(TextAssertion, method)):
|
|
28
|
+
return getattr(self, method)(actual, expect)
|
|
29
|
+
elif hasattr(ExcelAssertion, method) and callable(getattr(ExcelAssertion, method)):
|
|
30
|
+
try:
|
|
31
|
+
if isinstance(actual, str):
|
|
32
|
+
try:
|
|
33
|
+
actual_strip = actual.strip()
|
|
34
|
+
if actual_strip.startswith('{') or actual_strip.startswith('['):
|
|
35
|
+
actual = json.loads(actual)
|
|
36
|
+
except json.decoder.JSONDecodeError:
|
|
37
|
+
fixed_actual = re.sub(r'\\', r'\\\\', actual)
|
|
38
|
+
actual = json.loads(fixed_actual)
|
|
39
|
+
if isinstance(expect, str):
|
|
40
|
+
expect_strip = expect.strip()
|
|
41
|
+
if expect_strip.startswith('{') or expect_strip.startswith('['):
|
|
42
|
+
expect = json.loads(expect)
|
|
43
|
+
except json.decoder.JSONDecodeError:
|
|
44
|
+
raise XiaochaoToolsError(*ERROR_MSG_0019)
|
|
45
|
+
return getattr(self, method)(actual, expect)
|
|
46
|
+
elif hasattr(SqlAssertion, method) and callable(getattr(SqlAssertion, method)):
|
|
47
|
+
if self.mysql_connect is None:
|
|
48
|
+
raise XiaochaoToolsError(*ERROR_MSG_0021)
|
|
49
|
+
try:
|
|
50
|
+
if isinstance(expect, str):
|
|
51
|
+
expect_strip = expect.strip()
|
|
52
|
+
if expect_strip.startswith('{') or expect_strip.startswith('['):
|
|
53
|
+
expect = json.loads(expect)
|
|
54
|
+
except json.decoder.JSONDecodeError:
|
|
55
|
+
expect = expect
|
|
56
|
+
return getattr(self, method)(actual, expect)
|
|
57
|
+
elif hasattr(CustomAssertion, method) and callable(getattr(CustomAssertion, method)):
|
|
58
|
+
return getattr(self, method)(actual, expect)
|
|
59
|
+
else:
|
|
60
|
+
return getattr(self, method)(actual, expect)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
'XiaochaoAssertion',
|
|
66
|
+
'TextAssertion',
|
|
67
|
+
'FileAssertion',
|
|
68
|
+
'SqlAssertion',
|
|
69
|
+
'CustomAssertion',
|
|
70
|
+
'ExcelAssertion',
|
|
71
|
+
]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Project: 筱超测试平台
|
|
3
|
+
# @Description:
|
|
4
|
+
# @Time : 2025-07-04 14:09
|
|
5
|
+
# @Author : 筱超
|
|
6
|
+
from ...data_processor import DataProcessor
|
|
7
|
+
from ...decorator import sync_method_callback
|
|
8
|
+
from ...exceptions import XiaochaoToolsError
|
|
9
|
+
from ...exceptions.error_msg import ERROR_MSG_0061
|
|
10
|
+
from ...models import MethodModel
|
|
11
|
+
|
|
12
|
+
v = """
|
|
13
|
+
def func(self):
|
|
14
|
+
# 示例函数,函数名称必须是func,必须带有self参数,self.test_data.get_cache('key')可以获取到缓存中的数据,断言必须返回结果,因为返回的结果会作为提示显示在页面
|
|
15
|
+
user = self.test_data.get_cache('账号A')
|
|
16
|
+
print(user)
|
|
17
|
+
assert user == 'auto_te1st@qq.com', f'用户邮箱不匹配,实际={user},预期=auto_test@qq.com'
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CustomAssertion:
|
|
22
|
+
"""函数断言"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, test_data: DataProcessor):
|
|
25
|
+
self.test_data = test_data
|
|
26
|
+
|
|
27
|
+
@sync_method_callback('函数断言', '函数断言', 7, [
|
|
28
|
+
MethodModel(n='函数代码', f='actual', p='请输入一个python函数,函数名称必须是:func', d=True, v=v)])
|
|
29
|
+
def ass_func(self, actual, expect=None):
|
|
30
|
+
"""输入断言代码"""
|
|
31
|
+
try:
|
|
32
|
+
global_namespace = {}
|
|
33
|
+
exec(actual, global_namespace)
|
|
34
|
+
return global_namespace['func'](self)
|
|
35
|
+
except (KeyError, SyntaxError, TypeError):
|
|
36
|
+
import traceback
|
|
37
|
+
traceback.print_exc()
|
|
38
|
+
raise XiaochaoToolsError(*ERROR_MSG_0061)
|
|
39
|
+
|
|
40
|
+
def func(self):
|
|
41
|
+
# 示例函数,函数名称必须是func,必须带有self参数,self.test_data.get_cache('key')可以获取到缓存中的数据,断言必须返回结果,因为返回的结果会作为提示显示在页面
|
|
42
|
+
user = self.test_data.get_cache('账号A')
|
|
43
|
+
print(user)
|
|
44
|
+
assert user == 'auto_te1st@qq.com', f'用户邮箱不匹配,实际={user},预期=auto_test@qq.com'
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Project: 筱超测试平台
|
|
3
|
+
# @Description:
|
|
4
|
+
# @Time : 2025-07-04 10:43
|
|
5
|
+
# @Author : 筱超
|
|
6
|
+
|
|
7
|
+
from openpyxl import open
|
|
8
|
+
import requests
|
|
9
|
+
import tempfile
|
|
10
|
+
|
|
11
|
+
from xiaochaotools.assertion.text import TextAssertion
|
|
12
|
+
from xiaochaotools.decorator import sync_method_callback
|
|
13
|
+
from xiaochaotools.models import MethodModel
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
warnings.filterwarnings('ignore', message="Workbook contains no default style")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def open_excel_workbook(file_path, sheet_name=None):
|
|
20
|
+
"""
|
|
21
|
+
统一处理Excel文件打开逻辑,支持本地路径和URL下载
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
file_path (str): Excel文件路径或URL
|
|
25
|
+
sheet_name (str, optional): 工作表名称
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
tuple: (workbook, sheet) 工作簿对象和工作表对象
|
|
29
|
+
"""
|
|
30
|
+
# 判断是否为URL
|
|
31
|
+
is_url = file_path.startswith(('http://', 'https://'))
|
|
32
|
+
|
|
33
|
+
if is_url:
|
|
34
|
+
# 下载文件到临时位置
|
|
35
|
+
response = requests.get(file_path, proxies={'http': None, 'https': None})
|
|
36
|
+
response.raise_for_status()
|
|
37
|
+
|
|
38
|
+
# 创建临时文件
|
|
39
|
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx')
|
|
40
|
+
temp_file.write(response.content)
|
|
41
|
+
temp_file.close()
|
|
42
|
+
|
|
43
|
+
# 打开Excel文件
|
|
44
|
+
workbook = open(temp_file.name)
|
|
45
|
+
|
|
46
|
+
# 保存临时文件路径,以便后续清理
|
|
47
|
+
workbook.temp_file_path = temp_file.name
|
|
48
|
+
else:
|
|
49
|
+
# 直接打开本地文件
|
|
50
|
+
workbook = open(file_path)
|
|
51
|
+
workbook.temp_file_path = None
|
|
52
|
+
|
|
53
|
+
# 处理工作表
|
|
54
|
+
if sheet_name is None or (sheet_name == 'sheet1' and sheet_name not in workbook.sheetnames):
|
|
55
|
+
sheet = workbook.active
|
|
56
|
+
else:
|
|
57
|
+
sheet = workbook[sheet_name] if sheet_name else workbook.active
|
|
58
|
+
|
|
59
|
+
return workbook, sheet
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ExcelAssertion:
|
|
63
|
+
"""Excel文件"""
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
@sync_method_callback('文件断言', 'Excel断言', 0, [
|
|
67
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
68
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
69
|
+
MethodModel(n='预期值', f='expect', d=True, v={'第几行': '请输入要匹配第几行', '行数据': 'json格式的行数据'})
|
|
70
|
+
])
|
|
71
|
+
def assert_excel_row_data(actual: dict, expect: dict):
|
|
72
|
+
"""断言某一行数据"""
|
|
73
|
+
import json
|
|
74
|
+
file_path = actual.get('文件路径')
|
|
75
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
76
|
+
row_index = int(expect.get('第几行'))
|
|
77
|
+
row_data = expect.get('行数据')
|
|
78
|
+
if isinstance(row_data, str):
|
|
79
|
+
row_data = json.loads(row_data)
|
|
80
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
81
|
+
headers = [cell.value for cell in sheet[1]]
|
|
82
|
+
row = [cell.value for cell in sheet[row_index + 1]]
|
|
83
|
+
current_row = dict(zip(headers, row))
|
|
84
|
+
assert current_row == row_data, f"第{row_index}行数据不匹配: 实际={current_row}, 预期={row_data}"
|
|
85
|
+
return f"实际={current_row}, 预期={row_data}"
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
@sync_method_callback('文件断言', 'Excel断言', 1, [
|
|
89
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
90
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
91
|
+
MethodModel(n='预期值', f='expect', d=True, v={'预期行数': '请输入期望行数'})
|
|
92
|
+
])
|
|
93
|
+
def assert_excel_row_count(actual: dict, expect: dict):
|
|
94
|
+
"""断言工作表的行数"""
|
|
95
|
+
file_path = actual.get('文件路径')
|
|
96
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
97
|
+
expected_row_count = expect.get('预期行数')
|
|
98
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
99
|
+
actual_row_count = 0
|
|
100
|
+
for row in sheet.iter_rows(values_only=True):
|
|
101
|
+
if any(cell is not None for cell in row):
|
|
102
|
+
actual_row_count += 1
|
|
103
|
+
expected = int(expected_row_count)
|
|
104
|
+
assert actual_row_count == expected, f"行数不匹配, 实际={actual_row_count}, 预期={expected}"
|
|
105
|
+
return f'实际={actual_row_count}, 预期={expected}'
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
@sync_method_callback('文件断言', 'Excel断言', 2, [
|
|
109
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
110
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
111
|
+
MethodModel(n='预期值', f='expect', d=True, v={'预期行数': '请输入期望列数'})
|
|
112
|
+
])
|
|
113
|
+
def assert_excel_column_count(actual: dict, expect: dict):
|
|
114
|
+
"""断言工作表的列数"""
|
|
115
|
+
file_path = actual.get('文件路径')
|
|
116
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
117
|
+
expected_col_count = expect.get('预期列数')
|
|
118
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
119
|
+
actual_col_count = sheet.max_column
|
|
120
|
+
assert actual_col_count == expected_col_count, f"列数不匹配, 实际={actual_col_count}, 预期={expected_col_count}"
|
|
121
|
+
return f'实际={actual_col_count}, 预期={expected_col_count}'
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
@sync_method_callback('文件断言', 'Excel断言', 3, [
|
|
125
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
126
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
127
|
+
MethodModel(n='预期值', f='expect', d=True, v={'列字母': '请输入期望列数,示例:A',
|
|
128
|
+
'预期值列表': '请输入列表,示例:["任务类型", "301", "302", "303", "304"]'})
|
|
129
|
+
])
|
|
130
|
+
def assert_excel_column_values(actual: dict, expect: dict):
|
|
131
|
+
"""断言某一列的值"""
|
|
132
|
+
file_path = actual.get('文件路径')
|
|
133
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
134
|
+
column_letter = expect.get('列字母')
|
|
135
|
+
expected_values = expect.get('预期值列表')
|
|
136
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
137
|
+
actual_values = [cell.value for cell in sheet[column_letter]]
|
|
138
|
+
assert actual_values == expected_values, f"列{column_letter}的值不匹配, 实际={actual_values}, 预期={expected_values}"
|
|
139
|
+
return f'实际={actual_values}, 预期={expected_values}'
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
@sync_method_callback('文件断言', 'Excel断言', 4, [
|
|
143
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
144
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
145
|
+
MethodModel(n='预期值', f='expect', d=True, v={'预期表头': '请输入列表,示例: ["任务类型", "任务名称"]'})
|
|
146
|
+
])
|
|
147
|
+
def assert_excel_headers(actual: dict, expect: dict):
|
|
148
|
+
"""断言表头"""
|
|
149
|
+
file_path = actual.get('文件路径')
|
|
150
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
151
|
+
expected_headers = expect.get('预期表头')
|
|
152
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
153
|
+
actual_headers = [cell.value for cell in sheet[1]]
|
|
154
|
+
assert actual_headers == expected_headers, f"表头不匹配, 实际={actual_headers}, 预期={expected_headers}"
|
|
155
|
+
return f'实际={actual_headers}, 预期={expected_headers}'
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
@sync_method_callback('文件断言', 'Excel断言', 5, [
|
|
159
|
+
MethodModel(n='实际值', f='actual', d=True, v={'文件路径': '请输入文件的绝对路径,或者是下载地址'}),
|
|
160
|
+
MethodModel(n='预期值', f='expect', d=True, v={'工作表列表': '请输入列表,示例:["sheet1", "sheet2"]'})
|
|
161
|
+
])
|
|
162
|
+
def assert_excel_sheet_names(actual: dict, expect: dict):
|
|
163
|
+
"""断言工作表名称列表"""
|
|
164
|
+
file_path = actual.get('文件路径')
|
|
165
|
+
expected_sheet_names = expect.get('工作表列表')
|
|
166
|
+
workbook, _ = open_excel_workbook(file_path)
|
|
167
|
+
actual_sheet_names = workbook.sheetnames
|
|
168
|
+
assert actual_sheet_names == expected_sheet_names, f"工作表名称不匹配, 实际={actual_sheet_names}, 预期={expected_sheet_names}"
|
|
169
|
+
return f'实际={actual_sheet_names}, 预期={expected_sheet_names}'
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
@sync_method_callback('文件断言', 'Excel断言', 6, [
|
|
173
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
174
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
175
|
+
MethodModel(n='预期值', f='expect', d=True, v={'单元格': '请输入单元格,示例:G6', '预期值': '请输入预期值'})
|
|
176
|
+
])
|
|
177
|
+
def assert_excel_is_equal_to(actual: dict, expect: dict):
|
|
178
|
+
"""单元格相等"""
|
|
179
|
+
file_path = actual.get('文件路径')
|
|
180
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
181
|
+
cell_address = expect.get('单元格')
|
|
182
|
+
expected_value = expect.get('预期值')
|
|
183
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
184
|
+
actual_value = sheet[cell_address].value
|
|
185
|
+
try:
|
|
186
|
+
return TextAssertion.p_is_equal_to(float(actual_value), float(expected_value))
|
|
187
|
+
except (ValueError, TypeError, AssertionError):
|
|
188
|
+
pass
|
|
189
|
+
assert actual_value == expected_value or str(actual_value) == str(
|
|
190
|
+
expected_value), f"单元格{cell_address}的值不匹配, 实际={actual_value}, 预期={expected_value}"
|
|
191
|
+
return f'实际={str(actual_value)}, 预期={str(expected_value)}'
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
@sync_method_callback('文件断言', 'Excel断言', 7, [
|
|
195
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
196
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
197
|
+
MethodModel(n='预期值', f='expect', d=True, v={'单元格': '请输入单元格,示例:G6', '预期值': '请输入预期值'})
|
|
198
|
+
])
|
|
199
|
+
def assert_excel_is_not_equal_to(actual: dict, expect: dict):
|
|
200
|
+
"""单元格不相等"""
|
|
201
|
+
file_path = actual.get('文件路径')
|
|
202
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
203
|
+
cell_address = expect.get('单元格')
|
|
204
|
+
expected_value = expect.get('预期值')
|
|
205
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
206
|
+
actual_value = sheet[cell_address].value
|
|
207
|
+
try:
|
|
208
|
+
return TextAssertion.p_is_not_equal_to(float(actual_value), float(expected_value))
|
|
209
|
+
except (ValueError, TypeError, AssertionError):
|
|
210
|
+
pass
|
|
211
|
+
assert actual_value != expected_value and str(actual_value) != str(
|
|
212
|
+
expected_value), f"单元格{cell_address}的值不匹配, 实际={actual_value}, 预期={expected_value}"
|
|
213
|
+
return f'实际={str(actual_value)}, 预期={str(expected_value)}'
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
@sync_method_callback('文件断言', 'Excel断言', 8, [
|
|
217
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
218
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
219
|
+
MethodModel(n='预期值', f='expect', d=True, v={'单元格': '请输入单元格,示例:G6'})
|
|
220
|
+
])
|
|
221
|
+
def assert_excel_is_not_none(actual: dict, expect: dict):
|
|
222
|
+
"""单元格不是null"""
|
|
223
|
+
file_path = actual.get('文件路径')
|
|
224
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
225
|
+
cell_address = expect.get('单元格')
|
|
226
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
227
|
+
actual_value = sheet[cell_address].value
|
|
228
|
+
return TextAssertion.p_is_not_none(actual_value)
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
@sync_method_callback('文件断言', 'Excel断言', 9, [
|
|
232
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
233
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
234
|
+
MethodModel(n='预期值', f='expect', d=True, v={'单元格': '请输入单元格,示例:G6'})
|
|
235
|
+
])
|
|
236
|
+
def assert_excel_is_none(actual: dict, expect: dict):
|
|
237
|
+
"""单元格是null"""
|
|
238
|
+
file_path = actual.get('文件路径')
|
|
239
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
240
|
+
cell_address = expect.get('单元格')
|
|
241
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
242
|
+
actual_value = sheet[cell_address].value
|
|
243
|
+
return TextAssertion.p_is_none(actual_value)
|
|
244
|
+
|
|
245
|
+
@staticmethod
|
|
246
|
+
@sync_method_callback('文件断言', 'Excel断言', 10, [
|
|
247
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
248
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
249
|
+
MethodModel(n='预期值', f='expect', d=True, v={'单元格': '请输入单元格,示例:G6', '预期值': '请输入预期值'})
|
|
250
|
+
])
|
|
251
|
+
def assert_excel_contains(actual: dict, expect: dict):
|
|
252
|
+
"""单元格包含预期"""
|
|
253
|
+
file_path = actual.get('文件路径')
|
|
254
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
255
|
+
cell_address = expect.get('单元格')
|
|
256
|
+
expected_value = expect.get('预期值')
|
|
257
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
258
|
+
actual_value = sheet[cell_address].value
|
|
259
|
+
return TextAssertion.p_contains(actual_value, expected_value)
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
@sync_method_callback('文件断言', 'Excel断言', 11, [
|
|
263
|
+
MethodModel(n='实际值', f='actual', d=True,
|
|
264
|
+
v={'文件路径': '请输入文件的绝对路径,或者是下载地址', '工作表': 'sheet1'}),
|
|
265
|
+
MethodModel(n='预期值', f='expect', d=True, v={'单元格': '请输入单元格,示例:G6', '预期值': '请输入预期值'})
|
|
266
|
+
])
|
|
267
|
+
def assert_excel_not_contain(actual: dict, expect: dict):
|
|
268
|
+
"""单元格不包含预期"""
|
|
269
|
+
file_path = actual.get('文件路径')
|
|
270
|
+
sheet_name = actual.get('工作表', 'sheet1')
|
|
271
|
+
cell_address = expect.get('单元格')
|
|
272
|
+
expected_value = expect.get('预期值')
|
|
273
|
+
workbook, sheet = open_excel_workbook(file_path, sheet_name)
|
|
274
|
+
actual_value = sheet[cell_address].value
|
|
275
|
+
return TextAssertion.p_does_not_contain(actual_value, expected_value)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Project: 筱超测试平台
|
|
3
|
+
# @Description: txt文件断言
|
|
4
|
+
# @Time : 2025-07-04
|
|
5
|
+
# @Author : 筱超
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
import tempfile
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from xiaochaotools.decorator import sync_method_callback
|
|
12
|
+
from xiaochaotools.models import MethodModel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def read_txt_file(file_path):
|
|
16
|
+
"""
|
|
17
|
+
统一处理TXT文件读取逻辑,支持本地路径和URL下载
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
file_path (str): TXT文件路径或URL
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
str: 文件内容
|
|
24
|
+
"""
|
|
25
|
+
# 判断是否为URL
|
|
26
|
+
is_url = file_path.startswith(('http://', 'https://'))
|
|
27
|
+
|
|
28
|
+
if is_url:
|
|
29
|
+
# 下载文件内容
|
|
30
|
+
response = requests.get(file_path)
|
|
31
|
+
response.raise_for_status()
|
|
32
|
+
return response.text
|
|
33
|
+
else:
|
|
34
|
+
# 直接读取本地文件
|
|
35
|
+
with open(file_path, encoding='utf-8') as f:
|
|
36
|
+
return f.read()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TxtAssertion:
|
|
40
|
+
"""txt文件"""
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
@sync_method_callback('文件断言', 'txt断言', 0, [
|
|
44
|
+
MethodModel(n='实际值', f='actual', p='实际内容或文件路径', d=True),
|
|
45
|
+
MethodModel(n='预期值', f='expect', p='期望内容', d=True),
|
|
46
|
+
])
|
|
47
|
+
def assert_txt_equal(actual: str, expect: str):
|
|
48
|
+
"""内容完全相等"""
|
|
49
|
+
if actual.endswith('.txt') or actual.startswith(('http://', 'https://')):
|
|
50
|
+
actual_content = read_txt_file(actual)
|
|
51
|
+
else:
|
|
52
|
+
actual_content = actual
|
|
53
|
+
assert actual_content == expect, f'实际内容={actual_content}, 期望内容={expect}'
|
|
54
|
+
return f'实际内容={actual_content}, 期望内容={expect}'
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
@sync_method_callback('文件断言', 'txt断言', 1, [
|
|
58
|
+
MethodModel(n='实际值', f='actual', p='实际内容或文件路径', d=True),
|
|
59
|
+
MethodModel(n='预期值', f='expect', p='期望包含内容', d=True),
|
|
60
|
+
])
|
|
61
|
+
def assert_txt_contains(actual: str, expect: str):
|
|
62
|
+
"""内容包含指定字符串"""
|
|
63
|
+
if actual.endswith('.txt') or actual.startswith(('http://', 'https://')):
|
|
64
|
+
actual_content = read_txt_file(actual)
|
|
65
|
+
else:
|
|
66
|
+
actual_content = actual
|
|
67
|
+
assert expect in actual_content, f'实际内容={actual_content}, 期望包含={expect}'
|
|
68
|
+
return f'实际内容={actual_content}, 期望包含={expect}'
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
@sync_method_callback('文件断言', 'txt断言', 2, [
|
|
72
|
+
MethodModel(n='实际值', f='actual', p='实际内容或文件路径', d=True),
|
|
73
|
+
MethodModel(n='预期值', f='expect', p='期望长度', d=True),
|
|
74
|
+
])
|
|
75
|
+
def assert_txt_length_equal(actual: str, expect: int):
|
|
76
|
+
"""内容长度等于期望值"""
|
|
77
|
+
if actual.endswith('.txt') or actual.startswith(('http://', 'https://')):
|
|
78
|
+
actual_content = read_txt_file(actual)
|
|
79
|
+
else:
|
|
80
|
+
actual_content = actual
|
|
81
|
+
assert len(actual_content) == int(expect), f'实际长度={len(actual_content)}, 期望长度={expect}'
|
|
82
|
+
return f'实际长度={len(actual_content)}, 期望长度={expect}'
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
@sync_method_callback('文件断言', 'txt断言', 3, [
|
|
86
|
+
MethodModel(n='实际值', f='actual', p='实际内容或文件路径', d=True),
|
|
87
|
+
MethodModel(n='预期值', f='expect', p='期望开头内容', d=True),
|
|
88
|
+
])
|
|
89
|
+
def assert_txt_startswith(actual: str, expect: str):
|
|
90
|
+
"""内容以指定字符串开头"""
|
|
91
|
+
if actual.endswith('.txt') or actual.startswith(('http://', 'https://')):
|
|
92
|
+
actual_content = read_txt_file(actual)
|
|
93
|
+
else:
|
|
94
|
+
actual_content = actual
|
|
95
|
+
assert actual_content.startswith(expect), f'实际内容={actual_content}, 期望开头={expect}'
|
|
96
|
+
return f'实际内容={actual_content}, 期望开头={expect}'
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
@sync_method_callback('文件断言', 'txt断言', 4, [
|
|
100
|
+
MethodModel(n='实际值', f='actual', p='实际内容或文件路径', d=True),
|
|
101
|
+
MethodModel(n='预期值', f='expect', p='期望结尾内容', d=True),
|
|
102
|
+
])
|
|
103
|
+
def assert_txt_endswith(actual: str, expect: str):
|
|
104
|
+
"""内容以指定字符串结尾"""
|
|
105
|
+
if actual.endswith('.txt') or actual.startswith(('http://', 'https://')):
|
|
106
|
+
actual_content = read_txt_file(actual)
|
|
107
|
+
else:
|
|
108
|
+
actual_content = actual
|
|
109
|
+
assert actual_content.endswith(expect), f'实际内容={actual_content}, 期望结尾={expect}'
|
|
110
|
+
return f'实际内容={actual_content}, 期望结尾={expect}'
|