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.
Files changed (73) hide show
  1. xiaochaotools/__init__.py +5 -0
  2. xiaochaotools/assertion/__init__.py +71 -0
  3. xiaochaotools/assertion/custom/__init__.py +44 -0
  4. xiaochaotools/assertion/file/__init__.py +11 -0
  5. xiaochaotools/assertion/file/_excel.py +275 -0
  6. xiaochaotools/assertion/file/_txt.py +110 -0
  7. xiaochaotools/assertion/sql/__init__.py +10 -0
  8. xiaochaotools/assertion/sql/_sql.py +300 -0
  9. xiaochaotools/assertion/text/__init__.py +18 -0
  10. xiaochaotools/assertion/text/_contain.py +84 -0
  11. xiaochaotools/assertion/text/_matching.py +146 -0
  12. xiaochaotools/assertion/text/_whatis.py +75 -0
  13. xiaochaotools/assertion/text/_whatisit.py +108 -0
  14. xiaochaotools/dal/__init__.py +49 -0
  15. xiaochaotools/dal/accessors.py +111 -0
  16. xiaochaotools/dal/assertions.py +707 -0
  17. xiaochaotools/dal/bdd_steps.py +329 -0
  18. xiaochaotools/dal/core/__init__.py +36 -0
  19. xiaochaotools/dal/core/ast_nodes.py +307 -0
  20. xiaochaotools/dal/core/lexer.py +431 -0
  21. xiaochaotools/dal/core/operators.py +660 -0
  22. xiaochaotools/dal/core/parser.py +910 -0
  23. xiaochaotools/dal/core/parser_new.py +515 -0
  24. xiaochaotools/dal/core/parser_old.py +735 -0
  25. xiaochaotools/dal/core/runtime.py +531 -0
  26. xiaochaotools/dal/schema.py +414 -0
  27. xiaochaotools/data_processor/__init__.py +122 -0
  28. xiaochaotools/data_processor/_cache_tool.py +69 -0
  29. xiaochaotools/data_processor/_coding_tool.py +25 -0
  30. xiaochaotools/data_processor/_encryption_tool.py +47 -0
  31. xiaochaotools/data_processor/_json_tool.py +74 -0
  32. xiaochaotools/data_processor/_random_character_info_data.py +124 -0
  33. xiaochaotools/data_processor/_random_number_data.py +246 -0
  34. xiaochaotools/data_processor/_random_string_data.py +347 -0
  35. xiaochaotools/data_processor/_random_time_data.py +431 -0
  36. xiaochaotools/data_processor/_sql_cache.py +152 -0
  37. xiaochaotools/database/__init__.py +14 -0
  38. xiaochaotools/database/_async_mysql_connect.py +83 -0
  39. xiaochaotools/database/_mysql_connect.py +147 -0
  40. xiaochaotools/database/_mysql_pool.py +376 -0
  41. xiaochaotools/database/_sqlite_connect.py +41 -0
  42. xiaochaotools/decorator/__init__.py +21 -0
  43. xiaochaotools/decorator/convert_args.py +29 -0
  44. xiaochaotools/decorator/data_method.py +82 -0
  45. xiaochaotools/decorator/method_callback.py +66 -0
  46. xiaochaotools/decorator/retry.py +46 -0
  47. xiaochaotools/decorator/singleton.py +21 -0
  48. xiaochaotools/enums/__init__.py +13 -0
  49. xiaochaotools/enums/_base_enum.py +38 -0
  50. xiaochaotools/enums/_enums.py +57 -0
  51. xiaochaotools/exceptions/__init__.py +9 -0
  52. xiaochaotools/exceptions/_exceptions.py +15 -0
  53. xiaochaotools/exceptions/error_msg.py +26 -0
  54. xiaochaotools/log_collector/__init__.py +98 -0
  55. xiaochaotools/log_collector/_log_control.py +98 -0
  56. xiaochaotools/method.py +95 -0
  57. xiaochaotools/models/__init__.py +20 -0
  58. xiaochaotools/models/_models.py +97 -0
  59. xiaochaotools/monitoring/__init__.py +6 -0
  60. xiaochaotools/monitoring/base_monitor.py +192 -0
  61. xiaochaotools/notice/__init__.py +14 -0
  62. xiaochaotools/notice/_feishu_send.py +264 -0
  63. xiaochaotools/notice/_mail_send.py +251 -0
  64. xiaochaotools/notice/_wechat_send.py +166 -0
  65. xiaochaotools/xiaochaos/__init__.py +14 -0
  66. xiaochaotools/xiaochaos/core.py +217 -0
  67. xiaochaotools/xiaochaos/flow.py +127 -0
  68. xiaochaotools/xiaochaos/git.py +397 -0
  69. xiaochaotools-2.0.6.dist-info/METADATA +43 -0
  70. xiaochaotools-2.0.6.dist-info/RECORD +73 -0
  71. xiaochaotools-2.0.6.dist-info/WHEEL +5 -0
  72. xiaochaotools-2.0.6.dist-info/licenses/LICENSE +21 -0
  73. xiaochaotools-2.0.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Project: 筱超测试平台
3
+ # @Description:
4
+ # @Time : 2024-09-07 22:15
5
+ # @Author : 筱超
@@ -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,11 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Project: 筱超测试平台
3
+ # @Description:
4
+ # @Time : 2025-07-04 14:09
5
+ # @Author : 筱超
6
+ from ._excel import ExcelAssertion
7
+ from ._txt import TxtAssertion
8
+
9
+
10
+ class FileAssertion(ExcelAssertion, TxtAssertion):
11
+ """文件断言"""
@@ -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}'
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Project: 筱超测试平台
3
+ # @Description:
4
+ # @Time : 2025-07-04 14:09
5
+ # @Author : 筱超
6
+ from xiaochaotools.assertion.sql._sql import MysqlAssertion
7
+
8
+
9
+ class SqlAssertion(MysqlAssertion):
10
+ """SQL断言"""