pytest-dsl 0.3.1__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_dsl/core/dsl_executor.py +161 -48
- pytest_dsl/core/lexer.py +12 -1
- pytest_dsl/core/parser.py +25 -6
- pytest_dsl/core/parsetab.py +71 -66
- pytest_dsl/keywords/http_keywords.py +89 -89
- pytest_dsl/keywords/system_keywords.py +326 -0
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/METADATA +159 -11
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/RECORD +12 -12
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/WHEEL +1 -1
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/entry_points.txt +1 -0
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,10 @@
|
|
1
1
|
import allure
|
2
|
+
import time
|
3
|
+
import random
|
4
|
+
import string
|
5
|
+
import subprocess
|
6
|
+
import datetime
|
7
|
+
import logging
|
2
8
|
from pytest_dsl.core.keyword_manager import keyword_manager
|
3
9
|
|
4
10
|
|
@@ -15,3 +21,323 @@ def print_content(**kwargs):
|
|
15
21
|
])
|
16
22
|
def return_result(**kwargs):
|
17
23
|
return kwargs.get('result')
|
24
|
+
|
25
|
+
|
26
|
+
@keyword_manager.register('等待', [
|
27
|
+
{'name': '秒数', 'mapping': 'seconds', 'description': '等待的秒数,可以是小数'}
|
28
|
+
])
|
29
|
+
def wait_seconds(**kwargs):
|
30
|
+
"""等待指定的秒数
|
31
|
+
|
32
|
+
Args:
|
33
|
+
seconds: 等待的秒数,可以是小数表示毫秒级等待
|
34
|
+
"""
|
35
|
+
seconds = float(kwargs.get('seconds', 0))
|
36
|
+
with allure.step(f"等待 {seconds} 秒"):
|
37
|
+
time.sleep(seconds)
|
38
|
+
return True
|
39
|
+
|
40
|
+
|
41
|
+
@keyword_manager.register('获取当前时间', [
|
42
|
+
{'name': '格式', 'mapping': 'format', 'description': '时间格式,例如 "%Y-%m-%d %H:%M:%S",默认返回时间戳'},
|
43
|
+
{'name': '时区', 'mapping': 'timezone', 'description': '时区,例如 "Asia/Shanghai",默认为本地时区'}
|
44
|
+
])
|
45
|
+
def get_current_time(**kwargs):
|
46
|
+
"""获取当前时间
|
47
|
+
|
48
|
+
Args:
|
49
|
+
format: 时间格式,如果不提供则返回时间戳
|
50
|
+
timezone: 时区,默认为本地时区
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
str: 格式化的时间字符串或时间戳
|
54
|
+
"""
|
55
|
+
time_format = kwargs.get('format')
|
56
|
+
timezone = kwargs.get('timezone')
|
57
|
+
|
58
|
+
# 获取当前时间
|
59
|
+
if timezone:
|
60
|
+
import pytz
|
61
|
+
try:
|
62
|
+
tz = pytz.timezone(timezone)
|
63
|
+
current_time = datetime.datetime.now(tz)
|
64
|
+
except Exception as e:
|
65
|
+
allure.attach(
|
66
|
+
f"时区设置异常: {str(e)}",
|
67
|
+
name="时区设置异常",
|
68
|
+
attachment_type=allure.attachment_type.TEXT
|
69
|
+
)
|
70
|
+
current_time = datetime.datetime.now()
|
71
|
+
else:
|
72
|
+
current_time = datetime.datetime.now()
|
73
|
+
|
74
|
+
# 格式化时间
|
75
|
+
if time_format:
|
76
|
+
try:
|
77
|
+
result = current_time.strftime(time_format)
|
78
|
+
except Exception as e:
|
79
|
+
allure.attach(
|
80
|
+
f"时间格式化异常: {str(e)}",
|
81
|
+
name="时间格式化异常",
|
82
|
+
attachment_type=allure.attachment_type.TEXT
|
83
|
+
)
|
84
|
+
result = str(current_time)
|
85
|
+
else:
|
86
|
+
# 返回时间戳
|
87
|
+
result = str(int(current_time.timestamp()))
|
88
|
+
|
89
|
+
return result
|
90
|
+
|
91
|
+
|
92
|
+
@keyword_manager.register('生成随机字符串', [
|
93
|
+
{'name': '长度', 'mapping': 'length', 'description': '随机字符串的长度,默认为8'},
|
94
|
+
{'name': '类型', 'mapping': 'type',
|
95
|
+
'description': '字符类型:字母(letters)、数字(digits)、字母数字(alphanumeric)、全部(all),默认为字母数字'}
|
96
|
+
])
|
97
|
+
def generate_random_string(**kwargs):
|
98
|
+
"""生成随机字符串
|
99
|
+
|
100
|
+
Args:
|
101
|
+
length: 随机字符串的长度
|
102
|
+
type: 字符类型:字母、数字、字母数字、全部
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
str: 生成的随机字符串
|
106
|
+
"""
|
107
|
+
length = int(kwargs.get('length', 8))
|
108
|
+
char_type = kwargs.get('type', 'alphanumeric').lower()
|
109
|
+
|
110
|
+
# 根据类型选择字符集
|
111
|
+
if char_type == 'letters':
|
112
|
+
chars = string.ascii_letters
|
113
|
+
elif char_type == 'digits':
|
114
|
+
chars = string.digits
|
115
|
+
elif char_type == 'alphanumeric':
|
116
|
+
chars = string.ascii_letters + string.digits
|
117
|
+
elif char_type == 'all':
|
118
|
+
chars = string.ascii_letters + string.digits + string.punctuation
|
119
|
+
else:
|
120
|
+
# 默认使用字母数字
|
121
|
+
chars = string.ascii_letters + string.digits
|
122
|
+
|
123
|
+
# 生成随机字符串
|
124
|
+
result = ''.join(random.choice(chars) for _ in range(length))
|
125
|
+
|
126
|
+
with allure.step(f"生成随机字符串: 长度={length}, 类型={char_type}"):
|
127
|
+
allure.attach(
|
128
|
+
f"生成的随机字符串: {result}",
|
129
|
+
name="随机字符串",
|
130
|
+
attachment_type=allure.attachment_type.TEXT
|
131
|
+
)
|
132
|
+
|
133
|
+
return result
|
134
|
+
|
135
|
+
|
136
|
+
@keyword_manager.register('生成随机数', [
|
137
|
+
{'name': '最小值', 'mapping': 'min', 'description': '随机数的最小值,默认为0'},
|
138
|
+
{'name': '最大值', 'mapping': 'max', 'description': '随机数的最大值,默认为100'},
|
139
|
+
{'name': '小数位数', 'mapping': 'decimals', 'description': '小数位数,默认为0(整数)'}
|
140
|
+
])
|
141
|
+
def generate_random_number(**kwargs):
|
142
|
+
"""生成随机数
|
143
|
+
|
144
|
+
Args:
|
145
|
+
min: 随机数的最小值
|
146
|
+
max: 随机数的最大值
|
147
|
+
decimals: 小数位数,0表示整数
|
148
|
+
|
149
|
+
Returns:
|
150
|
+
int/float: 生成的随机数
|
151
|
+
"""
|
152
|
+
min_value = float(kwargs.get('min', 0))
|
153
|
+
max_value = float(kwargs.get('max', 100))
|
154
|
+
decimals = int(kwargs.get('decimals', 0))
|
155
|
+
|
156
|
+
if decimals <= 0:
|
157
|
+
# 生成整数
|
158
|
+
result = random.randint(int(min_value), int(max_value))
|
159
|
+
else:
|
160
|
+
# 生成浮点数
|
161
|
+
result = round(random.uniform(min_value, max_value), decimals)
|
162
|
+
|
163
|
+
with allure.step(f"生成随机数: 范围=[{min_value}, {max_value}], 小数位数={decimals}"):
|
164
|
+
allure.attach(
|
165
|
+
f"生成的随机数: {result}",
|
166
|
+
name="随机数",
|
167
|
+
attachment_type=allure.attachment_type.TEXT
|
168
|
+
)
|
169
|
+
|
170
|
+
return result
|
171
|
+
|
172
|
+
|
173
|
+
@keyword_manager.register('字符串操作', [
|
174
|
+
{'name': '操作', 'mapping': 'operation',
|
175
|
+
'description': '操作类型:拼接(concat)、替换(replace)、分割(split)、大写(upper)、小写(lower)、去空格(strip)'},
|
176
|
+
{'name': '字符串', 'mapping': 'string', 'description': '要操作的字符串'},
|
177
|
+
{'name': '参数1', 'mapping': 'param1', 'description': '操作参数1,根据操作类型不同而不同'},
|
178
|
+
{'name': '参数2', 'mapping': 'param2', 'description': '操作参数2,根据操作类型不同而不同'}
|
179
|
+
])
|
180
|
+
def string_operation(**kwargs):
|
181
|
+
"""字符串操作
|
182
|
+
|
183
|
+
Args:
|
184
|
+
operation: 操作类型
|
185
|
+
string: 要操作的字符串
|
186
|
+
param1: 操作参数1
|
187
|
+
param2: 操作参数2
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
str: 操作结果
|
191
|
+
"""
|
192
|
+
operation = kwargs.get('operation', '').lower()
|
193
|
+
string = str(kwargs.get('string', ''))
|
194
|
+
param1 = kwargs.get('param1', '')
|
195
|
+
param2 = kwargs.get('param2', '')
|
196
|
+
|
197
|
+
result = string
|
198
|
+
|
199
|
+
if operation == 'concat':
|
200
|
+
# 拼接字符串
|
201
|
+
result = string + str(param1)
|
202
|
+
elif operation == 'replace':
|
203
|
+
# 替换字符串
|
204
|
+
result = string.replace(str(param1), str(param2))
|
205
|
+
elif operation == 'split':
|
206
|
+
# 分割字符串
|
207
|
+
result = string.split(str(param1))
|
208
|
+
if param2 and param2.isdigit():
|
209
|
+
# 如果提供了索引,返回指定位置的元素
|
210
|
+
index = int(param2)
|
211
|
+
if 0 <= index < len(result):
|
212
|
+
result = result[index]
|
213
|
+
elif operation == 'upper':
|
214
|
+
# 转大写
|
215
|
+
result = string.upper()
|
216
|
+
elif operation == 'lower':
|
217
|
+
# 转小写
|
218
|
+
result = string.lower()
|
219
|
+
elif operation == 'strip':
|
220
|
+
# 去空格
|
221
|
+
result = string.strip()
|
222
|
+
else:
|
223
|
+
# 未知操作,返回原字符串
|
224
|
+
allure.attach(
|
225
|
+
f"未知的字符串操作: {operation}",
|
226
|
+
name="字符串操作错误",
|
227
|
+
attachment_type=allure.attachment_type.TEXT
|
228
|
+
)
|
229
|
+
|
230
|
+
with allure.step(f"字符串操作: {operation}"):
|
231
|
+
allure.attach(
|
232
|
+
f"原字符串: {string}\n操作: {operation}\n参数1: {param1}\n参数2: {param2}\n结果: {result}",
|
233
|
+
name="字符串操作结果",
|
234
|
+
attachment_type=allure.attachment_type.TEXT
|
235
|
+
)
|
236
|
+
|
237
|
+
return result
|
238
|
+
|
239
|
+
|
240
|
+
@keyword_manager.register('日志', [
|
241
|
+
{'name': '级别', 'mapping': 'level',
|
242
|
+
'description': '日志级别:DEBUG, INFO, WARNING, ERROR, CRITICAL,默认为INFO'},
|
243
|
+
{'name': '消息', 'mapping': 'message', 'description': '日志消息内容'}
|
244
|
+
])
|
245
|
+
def log_message(**kwargs):
|
246
|
+
"""记录日志
|
247
|
+
|
248
|
+
Args:
|
249
|
+
level: 日志级别
|
250
|
+
message: 日志消息内容
|
251
|
+
"""
|
252
|
+
level = kwargs.get('level', 'INFO').upper()
|
253
|
+
message = kwargs.get('message', '')
|
254
|
+
|
255
|
+
# 获取日志级别
|
256
|
+
log_level = getattr(logging, level, logging.INFO)
|
257
|
+
|
258
|
+
# 记录日志
|
259
|
+
logging.log(log_level, message)
|
260
|
+
|
261
|
+
with allure.step(f"记录日志: [{level}] {message}"):
|
262
|
+
allure.attach(
|
263
|
+
f"日志级别: {level}\n日志消息: {message}",
|
264
|
+
name="日志记录",
|
265
|
+
attachment_type=allure.attachment_type.TEXT
|
266
|
+
)
|
267
|
+
|
268
|
+
return True
|
269
|
+
|
270
|
+
|
271
|
+
@keyword_manager.register('执行命令', [
|
272
|
+
{'name': '命令', 'mapping': 'command', 'description': '要执行的系统命令'},
|
273
|
+
{'name': '超时', 'mapping': 'timeout', 'description': '命令执行超时时间(秒),默认为60秒'},
|
274
|
+
{'name': '捕获输出', 'mapping': 'capture_output', 'description': '是否捕获命令输出,默认为True'}
|
275
|
+
])
|
276
|
+
def execute_command(**kwargs):
|
277
|
+
"""执行系统命令
|
278
|
+
|
279
|
+
Args:
|
280
|
+
command: 要执行的系统命令
|
281
|
+
timeout: 命令执行超时时间(秒)
|
282
|
+
capture_output: 是否捕获命令输出
|
283
|
+
|
284
|
+
Returns:
|
285
|
+
dict: 包含返回码、标准输出和标准错误的字典
|
286
|
+
"""
|
287
|
+
command = kwargs.get('command', '')
|
288
|
+
timeout = float(kwargs.get('timeout', 60))
|
289
|
+
capture_output = kwargs.get('capture_output', True)
|
290
|
+
|
291
|
+
with allure.step(f"执行命令: {command}"):
|
292
|
+
try:
|
293
|
+
# 执行命令
|
294
|
+
result = subprocess.run(
|
295
|
+
command,
|
296
|
+
shell=True,
|
297
|
+
timeout=timeout,
|
298
|
+
capture_output=capture_output,
|
299
|
+
text=True
|
300
|
+
)
|
301
|
+
|
302
|
+
# 构建结果字典
|
303
|
+
command_result = {
|
304
|
+
'returncode': result.returncode,
|
305
|
+
'stdout': result.stdout if capture_output else '',
|
306
|
+
'stderr': result.stderr if capture_output else ''
|
307
|
+
}
|
308
|
+
|
309
|
+
# 记录执行结果
|
310
|
+
allure.attach(
|
311
|
+
f"命令: {command}\n返回码: {result.returncode}\n"
|
312
|
+
f"标准输出: {result.stdout if capture_output else '未捕获'}\n"
|
313
|
+
f"标准错误: {result.stderr if capture_output else '未捕获'}",
|
314
|
+
name="命令执行结果",
|
315
|
+
attachment_type=allure.attachment_type.TEXT
|
316
|
+
)
|
317
|
+
|
318
|
+
return command_result
|
319
|
+
|
320
|
+
except subprocess.TimeoutExpired:
|
321
|
+
# 命令执行超时
|
322
|
+
allure.attach(
|
323
|
+
f"命令执行超时: {command} (超时: {timeout}秒)",
|
324
|
+
name="命令执行超时",
|
325
|
+
attachment_type=allure.attachment_type.TEXT
|
326
|
+
)
|
327
|
+
return {
|
328
|
+
'returncode': -1,
|
329
|
+
'stdout': '',
|
330
|
+
'stderr': f'Command timed out after {timeout} seconds'
|
331
|
+
}
|
332
|
+
except Exception as e:
|
333
|
+
# 其他异常
|
334
|
+
allure.attach(
|
335
|
+
f"命令执行异常: {str(e)}",
|
336
|
+
name="命令执行异常",
|
337
|
+
attachment_type=allure.attachment_type.TEXT
|
338
|
+
)
|
339
|
+
return {
|
340
|
+
'returncode': -1,
|
341
|
+
'stdout': '',
|
342
|
+
'stderr': str(e)
|
343
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pytest-dsl
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
4
4
|
Summary: A DSL testing framework based on pytest
|
5
5
|
Author: Chen Shuanglin
|
6
6
|
License: MIT
|
@@ -8,13 +8,13 @@ Project-URL: Homepage, https://github.com/felix-1991/pytest-dsl
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/felix-1991/pytest-dsl/issues
|
9
9
|
Classifier: Framework :: Pytest
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
11
|
-
Classifier: Programming Language :: Python :: 3.8
|
12
11
|
Classifier: Programming Language :: Python :: 3.9
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
16
16
|
Classifier: Operating System :: OS Independent
|
17
|
-
Requires-Python: >=3.
|
17
|
+
Requires-Python: >=3.9
|
18
18
|
Description-Content-Type: text/markdown
|
19
19
|
License-File: LICENSE
|
20
20
|
Requires-Dist: pytest>=7.0.0
|
@@ -26,6 +26,7 @@ Requires-Dist: jsonpath-ng>=1.5.0
|
|
26
26
|
Requires-Dist: requests>=2.28.0
|
27
27
|
Requires-Dist: lxml>=4.9.0
|
28
28
|
Requires-Dist: jsonschema>=4.17.0
|
29
|
+
Requires-Dist: pytz>=2023.3
|
29
30
|
Dynamic: license-file
|
30
31
|
|
31
32
|
# pytest-dsl: 强大的关键字驱动测试自动化框架
|
@@ -149,11 +150,11 @@ pytest-dsl允许在DSL文件中直接定义自定义关键字,类似于编程
|
|
149
150
|
@keyword 拼接字符串 (前缀, 后缀="默认后缀") do
|
150
151
|
# 直接使用关键字参数
|
151
152
|
[打印],内容: "拼接前缀: ${前缀} 和后缀: ${后缀}"
|
152
|
-
|
153
|
+
|
153
154
|
# 保存到变量中
|
154
155
|
结果变量 = "${前缀}${后缀}"
|
155
156
|
[打印],内容: "拼接结果: ${结果变量}"
|
156
|
-
|
157
|
+
|
157
158
|
# 返回结果
|
158
159
|
return ${结果变量}
|
159
160
|
end
|
@@ -188,11 +189,11 @@ end
|
|
188
189
|
@keyword 拼接字符串 (前缀, 后缀="我是默认值哦") do
|
189
190
|
# 直接使用关键字参数
|
190
191
|
[打印],内容:'拼接前缀: ${前缀} 和后缀: ${后缀}'
|
191
|
-
|
192
|
+
|
192
193
|
# 保存到变量中
|
193
194
|
结果变量 = "${前缀}${后缀}"
|
194
195
|
[打印],内容:'拼接结果: ${结果变量}'
|
195
|
-
|
196
|
+
|
196
197
|
# 返回结果
|
197
198
|
return ${结果变量}
|
198
199
|
end
|
@@ -311,7 +312,7 @@ from pytest_dsl.core.auto_decorator import auto_dsl
|
|
311
312
|
@auto_dsl("./api_tests") # 加载指定目录下所有的.auto或.dsl文件
|
312
313
|
class TestAPI:
|
313
314
|
"""API测试类
|
314
|
-
|
315
|
+
|
315
316
|
该类将自动加载api_tests目录下的所有DSL文件作为测试方法
|
316
317
|
"""
|
317
318
|
pass
|
@@ -330,6 +331,31 @@ pytest test_api.py
|
|
330
331
|
pytest -v --alluredir=./reports
|
331
332
|
```
|
332
333
|
|
334
|
+
### 使用Allure生成和查看报告
|
335
|
+
|
336
|
+
pytest-dsl已与Allure报告框架集成,可以生成美观、交互式的测试报告。
|
337
|
+
|
338
|
+
```bash
|
339
|
+
# 运行测试并生成Allure报告数据
|
340
|
+
pytest --alluredir=./allure-results
|
341
|
+
|
342
|
+
# 生成HTML报告并启动本地服务器查看
|
343
|
+
allure serve ./allure-results
|
344
|
+
|
345
|
+
# 或生成HTML报告到指定目录
|
346
|
+
allure generate ./allure-results -o ./allure-report
|
347
|
+
# 然后可以打开 ./allure-report/index.html 查看报告
|
348
|
+
```
|
349
|
+
|
350
|
+
Allure报告会自动包含以下信息:
|
351
|
+
- 测试步骤和执行状态
|
352
|
+
- HTTP请求和响应详情
|
353
|
+
- 断言结果和失败原因
|
354
|
+
- 测试执行时间和性能数据
|
355
|
+
- 测试标签和分类信息
|
356
|
+
|
357
|
+
通过Allure报告,您可以更直观地分析测试结果,快速定位问题。
|
358
|
+
|
333
359
|
## 更多功能
|
334
360
|
|
335
361
|
### 断言重试功能
|
@@ -386,7 +412,7 @@ def call_microservice(**kwargs):
|
|
386
412
|
method = kwargs.get('method_name')
|
387
413
|
params = kwargs.get('params', {})
|
388
414
|
context = kwargs.get('context')
|
389
|
-
|
415
|
+
|
390
416
|
# 实现微服务调用逻辑
|
391
417
|
result = your_microservice_client.call(service, method, params)
|
392
418
|
return result
|
@@ -427,13 +453,135 @@ def call_microservice(**kwargs):
|
|
427
453
|
- **完整测试生命周期**:内置teardown、变量管理和断言机制
|
428
454
|
- **非侵入式设计**:以"旁路模式"扩展现有pytest项目,不影响原有测试代码
|
429
455
|
|
456
|
+
## 远程关键字功能
|
457
|
+
|
458
|
+
pytest-dsl支持远程关键字调用,允许您在不同的机器或服务上执行关键字,实现分布式测试。
|
459
|
+
|
460
|
+
### 启动远程关键字服务
|
461
|
+
|
462
|
+
安装pytest-dsl后,可以使用内置命令启动远程关键字服务:
|
463
|
+
|
464
|
+
```bash
|
465
|
+
# 使用默认配置启动(localhost:8270)
|
466
|
+
pytest-dsl-server
|
467
|
+
|
468
|
+
# 自定义主机和端口
|
469
|
+
pytest-dsl-server --host 0.0.0.0 --port 8888
|
470
|
+
|
471
|
+
# 使用API密钥保护服务
|
472
|
+
pytest-dsl-server --api-key your_secret_key
|
473
|
+
```
|
474
|
+
|
475
|
+
#### 分布式测试环境配置
|
476
|
+
|
477
|
+
在分布式测试环境中,您可以在多台机器上启动远程关键字服务:
|
478
|
+
|
479
|
+
1. **主测试机**:运行测试脚本的机器
|
480
|
+
2. **远程执行机**:运行远程关键字服务的机器
|
481
|
+
|
482
|
+
配置步骤:
|
483
|
+
|
484
|
+
1. 在每台远程执行机上安装pytest-dsl:
|
485
|
+
```bash
|
486
|
+
pip install pytest-dsl
|
487
|
+
```
|
488
|
+
|
489
|
+
2. 在每台远程执行机上启动远程关键字服务:
|
490
|
+
```bash
|
491
|
+
# 确保监听所有网络接口,以便外部可访问
|
492
|
+
pytest-dsl-server --host 0.0.0.0 --port 8270
|
493
|
+
```
|
494
|
+
|
495
|
+
3. 在主测试机上编写测试脚本,使用`@remote`指令连接到远程服务:
|
496
|
+
```python
|
497
|
+
# 连接到多台远程执行机
|
498
|
+
@remote: "http://machine1-ip:8270/" as machine1
|
499
|
+
@remote: "http://machine2-ip:8270/" as machine2
|
500
|
+
|
501
|
+
# 在不同机器上执行关键字
|
502
|
+
machine1|[打印],内容: "在机器1上执行"
|
503
|
+
machine2|[打印],内容: "在机器2上执行"
|
504
|
+
```
|
505
|
+
|
506
|
+
### 远程关键字语法
|
507
|
+
|
508
|
+
```python
|
509
|
+
# 导入远程关键字服务器
|
510
|
+
@remote: "http://keyword-server:8270/" as machineone
|
511
|
+
@remote: "http://keyword-server2:8270/" as machinetwo
|
512
|
+
|
513
|
+
# 远程关键字调用
|
514
|
+
machineone|[打印],内容: "这是通过远程服务器执行的关键字"
|
515
|
+
结果 = machineone|[拼接字符串],前缀: "Hello, ",后缀: "Remote World!"
|
516
|
+
```
|
517
|
+
|
518
|
+
### 远程关键字测试示例
|
519
|
+
|
520
|
+
```python
|
521
|
+
@name: "远程关键字测试"
|
522
|
+
@description: "测试远程关键字的基本功能"
|
523
|
+
@tags: ["remote", "keywords"]
|
524
|
+
@author: "Felix"
|
525
|
+
@date: 2024-05-21
|
526
|
+
|
527
|
+
# 导入远程关键字服务器
|
528
|
+
@remote: "http://localhost:8270/" as machineone
|
529
|
+
|
530
|
+
# 基本打印测试
|
531
|
+
machineone|[打印],内容: "这是通过远程服务器执行的关键字"
|
532
|
+
|
533
|
+
# 随机数生成测试
|
534
|
+
随机数 = [生成随机数],最小值: 1,最大值: 100
|
535
|
+
machineone|[打印],内容: "远程生成的随机数: ${随机数}"
|
536
|
+
```
|
537
|
+
|
538
|
+
> **注意**:当前远程关键字模式在HTTP请求关键字上支持的不是太好,后续会优化关键字实现,提升远程关键字的功能和稳定性。
|
539
|
+
|
540
|
+
### 远程关键字服务安全性
|
541
|
+
|
542
|
+
在生产环境中使用远程关键字服务时,请注意以下安全建议:
|
543
|
+
|
544
|
+
1. **使用API密钥认证**:
|
545
|
+
```bash
|
546
|
+
pytest-dsl-server --api-key your_secure_key
|
547
|
+
```
|
548
|
+
然后在测试脚本中使用API密钥连接:
|
549
|
+
```python
|
550
|
+
@remote: "http://server:8270/?api_key=your_secure_key" as secure_server
|
551
|
+
```
|
552
|
+
|
553
|
+
2. **限制网络访问**:
|
554
|
+
- 在内部网络或VPN中使用远程关键字服务
|
555
|
+
- 使用防火墙限制对服务端口的访问
|
556
|
+
- 考虑使用SSH隧道连接到远程服务
|
557
|
+
|
558
|
+
3. **监控服务**:
|
559
|
+
- 定期检查服务日志
|
560
|
+
- 监控异常访问模式
|
561
|
+
- 在不需要时关闭服务
|
562
|
+
|
563
|
+
### 远程关键字最佳实践
|
564
|
+
|
565
|
+
1. **合理分配关键字**:
|
566
|
+
- 将计算密集型关键字放在性能更好的机器上
|
567
|
+
- 将特定环境依赖的关键字放在对应环境的机器上
|
568
|
+
|
569
|
+
2. **错误处理**:
|
570
|
+
- 添加适当的错误处理机制,处理远程服务不可用的情况
|
571
|
+
- 使用超时设置避免长时间等待
|
572
|
+
|
573
|
+
3. **变量传递**:
|
574
|
+
- 注意远程关键字执行后,变量会返回到本地上下文
|
575
|
+
- 大型数据应考虑使用文件或数据库共享,而不是直接通过变量传递
|
576
|
+
|
430
577
|
## 进阶文档
|
431
578
|
|
432
579
|
- [完整DSL语法指南](./docs/自动化关键字DSL语法设计.md)
|
433
|
-
- [创建自定义关键字](./pytest_dsl/docs/custom_keywords.md)
|
580
|
+
- [创建自定义关键字](./pytest_dsl/docs/custom_keywords.md)
|
434
581
|
- [HTTP测试关键字](./docs/api.md)
|
435
582
|
- [断言关键字详解](./docs/assertion_keywords.md)
|
436
583
|
- [HTTP断言重试机制](./docs/http_assertion_retry.md)
|
584
|
+
- [远程关键字语法示例](./docs/remote_syntax_example.md)
|
437
585
|
|
438
586
|
## 贡献与支持
|
439
587
|
|
@@ -445,4 +593,4 @@ MIT License
|
|
445
593
|
|
446
594
|
---
|
447
595
|
|
448
|
-
开始使用pytest-dsl,释放测试自动化的无限可能!
|
596
|
+
开始使用pytest-dsl,释放测试自动化的无限可能!
|
@@ -10,15 +10,15 @@ pytest_dsl/core/auto_decorator.py,sha256=uEW1t60E_saDLY5MwR2sGTK8IAtCZKruEPq-fRT
|
|
10
10
|
pytest_dsl/core/auto_directory.py,sha256=UF3Xpv4vtwvw0atS1NyChnL2XrnzwLST4H2jwlFip1E,2617
|
11
11
|
pytest_dsl/core/context.py,sha256=fXpVQon666Lz_PCexjkWMBBr-Xcd1SkDMp2vHar6eIs,664
|
12
12
|
pytest_dsl/core/custom_keyword_manager.py,sha256=QY-wXvzON2kkFwojBSXqWcQpo_aKixKhG-rsacV8gzE,7955
|
13
|
-
pytest_dsl/core/dsl_executor.py,sha256=
|
13
|
+
pytest_dsl/core/dsl_executor.py,sha256=fd-awPL1j1RCZ00fD3AeSwFcHNZzVx_dQe5IIvNe6rE,24127
|
14
14
|
pytest_dsl/core/dsl_executor_utils.py,sha256=cFoR2p3qQ2pb-UhkoefleK-zbuFqf0aBLh2Rlp8Ebs4,2180
|
15
15
|
pytest_dsl/core/global_context.py,sha256=NcEcS2V61MT70tgAsGsFWQq0P3mKjtHQr1rgT3yTcyY,3535
|
16
16
|
pytest_dsl/core/http_client.py,sha256=1AHqtM_fkXf8JrM0ljMsJwUkyt-ysjR16NoyCckHfGc,15810
|
17
17
|
pytest_dsl/core/http_request.py,sha256=yjcoCN0E49lm44WAO3H73E6Zm-5FuEm1swAIt6E3FFo,55404
|
18
18
|
pytest_dsl/core/keyword_manager.py,sha256=FtPsXlI7PxvVQMJfDN_nQYvRhkag5twvaHXjELQsCEo,4068
|
19
|
-
pytest_dsl/core/lexer.py,sha256=
|
20
|
-
pytest_dsl/core/parser.py,sha256=
|
21
|
-
pytest_dsl/core/parsetab.py,sha256=
|
19
|
+
pytest_dsl/core/lexer.py,sha256=nrpprs_a0-x5xQuJ9wo3gz8s03UJzYfri_WXKhTSvmw,3559
|
20
|
+
pytest_dsl/core/parser.py,sha256=vvdsFc8vi-msa6lM9Rr5eTjJAUJNjQINLnB15AhTJ5E,10039
|
21
|
+
pytest_dsl/core/parsetab.py,sha256=LSwz0uMGWmmylfrx7daID1TPFog-PhTyWlnSqM1jAXg,24049
|
22
22
|
pytest_dsl/core/plugin_discovery.py,sha256=jqTX2UzQMCjy6lQV5h2HX63YAdkm25dyfxrLfFp6Wag,6744
|
23
23
|
pytest_dsl/core/utils.py,sha256=INyuWAX_yhgU9JwSUxVjoB1iI1J6Gy4cVEXZsjJTAOg,4765
|
24
24
|
pytest_dsl/core/variable_utils.py,sha256=6uY-SWtGMyVvpFb8Sw-5u8oPzNnPQKl0pt9-8mBpwac,9167
|
@@ -58,11 +58,11 @@ pytest_dsl/examples/quickstart/loops.auto,sha256=2UPuNhvErCoPis-nDStSLj5EY-lo1FK
|
|
58
58
|
pytest_dsl/keywords/__init__.py,sha256=5aiyPU_t1UiB2MEZ6M9ffOKnV1mFT_2YHxnZvyWaBNI,372
|
59
59
|
pytest_dsl/keywords/assertion_keywords.py,sha256=H0vNCvfG3h8R3ST6C5sVTEH8OTWj3x94cAzqSNBBRLI,22827
|
60
60
|
pytest_dsl/keywords/global_keywords.py,sha256=RZcJ9ksfXZaRvds4247LFXu-8a0LFqTvVaD4n98GUrk,1410
|
61
|
-
pytest_dsl/keywords/http_keywords.py,sha256=
|
62
|
-
pytest_dsl/keywords/system_keywords.py,sha256=
|
63
|
-
pytest_dsl-0.
|
64
|
-
pytest_dsl-0.
|
65
|
-
pytest_dsl-0.
|
66
|
-
pytest_dsl-0.
|
67
|
-
pytest_dsl-0.
|
68
|
-
pytest_dsl-0.
|
61
|
+
pytest_dsl/keywords/http_keywords.py,sha256=Dxz_ebtGoy0D4Almn5LtBD9RSZnkeoHhygoGNcBSkOw,24810
|
62
|
+
pytest_dsl/keywords/system_keywords.py,sha256=n_jRrMvSv2v6Pm_amokfyLNVOLYP7CFWbBE3_dlO7h4,11299
|
63
|
+
pytest_dsl-0.5.0.dist-info/licenses/LICENSE,sha256=Rguy8cb9sYhK6cmrBdXvwh94rKVDh2tVZEWptsHIsVM,1071
|
64
|
+
pytest_dsl-0.5.0.dist-info/METADATA,sha256=jhvveN2EgoKR5TvJmmR7ONGQXyFceopAea13ZPhO8xQ,16405
|
65
|
+
pytest_dsl-0.5.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
66
|
+
pytest_dsl-0.5.0.dist-info/entry_points.txt,sha256=qqxFwSHQtjoae8_WWiO6w8y0N9nC4H8eFRvOnr_OipE,152
|
67
|
+
pytest_dsl-0.5.0.dist-info/top_level.txt,sha256=4CrSx4uNqxj7NvK6k1y2JZrSrJSzi-UvPZdqpUhumWM,11
|
68
|
+
pytest_dsl-0.5.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|