pytest-dsl 0.6.0__tar.gz → 0.8.0__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_dsl-0.8.0/PKG-INFO +1088 -0
- pytest_dsl-0.8.0/README.md +1057 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pyproject.toml +1 -1
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/dsl_executor.py +96 -15
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/lexer.py +21 -5
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/parser.py +68 -5
- pytest_dsl-0.8.0/pytest_dsl/core/parsetab.py +130 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/utils.py +43 -23
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/variable_utils.py +215 -70
- pytest_dsl-0.8.0/pytest_dsl.egg-info/PKG-INFO +1088 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl.egg-info/SOURCES.txt +1 -1
- pytest_dsl-0.8.0/tests/test_enhanced_variable_access.py +164 -0
- pytest_dsl-0.6.0/PKG-INFO +0 -725
- pytest_dsl-0.6.0/README.md +0 -694
- pytest_dsl-0.6.0/pytest_dsl/core/parsetab.py +0 -114
- pytest_dsl-0.6.0/pytest_dsl/parsetab.py +0 -69
- pytest_dsl-0.6.0/pytest_dsl.egg-info/PKG-INFO +0 -725
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/LICENSE +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/MANIFEST.in +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/__init__.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/cli.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/conftest_adapter.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/__init__.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/auth_provider.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/auto_decorator.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/auto_directory.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/context.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/custom_keyword_manager.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/dsl_executor_utils.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/global_context.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/http_client.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/http_request.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/keyword_manager.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/plugin_discovery.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/yaml_loader.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/core/yaml_vars.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/docs/custom_keywords.md +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/__init__.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/assert/assertion_example.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/assert/boolean_test.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/assert/expression_test.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/custom/test_advanced_keywords.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/custom/test_custom_keywords.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/custom/test_default_values.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/__init__.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/builtin_auth_test.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/file_reference_test.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/http_advanced.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/http_example.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/http_length_test.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/http_retry_assertions.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/http_with_yaml.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/new_retry_test.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/retry_assertions_only.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/retry_config_only.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/retry_debug.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/retry_with_fix.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/simple_retry.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/http/vars.yaml +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/quickstart/api_basics.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/quickstart/assertions.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/quickstart/loops.auto +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/test_assert.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/test_custom_keyword.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/test_http.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/examples/test_quickstart.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/keywords/__init__.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/keywords/assertion_keywords.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/keywords/global_keywords.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/keywords/http_keywords.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/keywords/system_keywords.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/main_adapter.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl/plugin.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl.egg-info/dependency_links.txt +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl.egg-info/entry_points.txt +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl.egg-info/requires.txt +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/pytest_dsl.egg-info/top_level.txt +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/setup.cfg +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/setup.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/tests/test_end_to_end_seamless.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/tests/test_http_assertions_extractors.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/tests/test_seamless_variable_sync.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/tests/test_variable_sync.py +0 -0
- {pytest_dsl-0.6.0 → pytest_dsl-0.8.0}/tests/test_variable_sync_demo.py +0 -0
@@ -0,0 +1,1088 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: pytest-dsl
|
3
|
+
Version: 0.8.0
|
4
|
+
Summary: A DSL testing framework based on pytest
|
5
|
+
Author: Chen Shuanglin
|
6
|
+
License: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/felix-1991/pytest-dsl
|
8
|
+
Project-URL: Bug Tracker, https://github.com/felix-1991/pytest-dsl/issues
|
9
|
+
Classifier: Framework :: Pytest
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
16
|
+
Classifier: Operating System :: OS Independent
|
17
|
+
Requires-Python: >=3.9
|
18
|
+
Description-Content-Type: text/markdown
|
19
|
+
License-File: LICENSE
|
20
|
+
Requires-Dist: pytest>=7.0.0
|
21
|
+
Requires-Dist: allure-pytest>=2.9.0
|
22
|
+
Requires-Dist: ply>=3.11
|
23
|
+
Requires-Dist: filelock>=3.17.0
|
24
|
+
Requires-Dist: PyYAML==6.0.2
|
25
|
+
Requires-Dist: jsonpath-ng>=1.5.0
|
26
|
+
Requires-Dist: requests>=2.28.0
|
27
|
+
Requires-Dist: lxml>=4.9.0
|
28
|
+
Requires-Dist: jsonschema>=4.17.0
|
29
|
+
Requires-Dist: pytz>=2023.3
|
30
|
+
Dynamic: license-file
|
31
|
+
|
32
|
+
# pytest-dsl: 强大的关键字驱动测试自动化框架
|
33
|
+
|
34
|
+
[](https://python.org)
|
35
|
+
[](LICENSE)
|
36
|
+
[](https://pypi.org/project/pytest-dsl/)
|
37
|
+
|
38
|
+
> 🚀 **让测试自动化变得简单直观** - 使用自然语言风格的DSL编写测试,无需复杂编程技能
|
39
|
+
|
40
|
+
pytest-dsl是一个革命性的关键字驱动测试框架,基于pytest构建,通过自定义的领域特定语言(DSL)让测试编写变得像写文档一样简单。无论是API测试、UI测试还是其他自动化场景,都能轻松应对。
|
41
|
+
|
42
|
+
## ✨ 核心特性
|
43
|
+
|
44
|
+
- 🎯 **门槛上手低** - 自然语言风格,只需少量编程基础
|
45
|
+
- 🔧 **高度可扩展** - 轻松创建自定义关键字
|
46
|
+
- 🌐 **分布式执行** - 支持远程关键字调用
|
47
|
+
- 🔄 **无缝集成** - 完美兼容pytest生态
|
48
|
+
- 📊 **丰富报告** - 集成Allure测试报告
|
49
|
+
- 🛡️ **企业级** - 支持变量管理、环境隔离
|
50
|
+
|
51
|
+
## 🚀 5分钟快速开始
|
52
|
+
|
53
|
+
### 第一步:安装
|
54
|
+
|
55
|
+
```bash
|
56
|
+
# 使用 pip 安装
|
57
|
+
pip install pytest-dsl
|
58
|
+
|
59
|
+
# 或使用 uv 安装(推荐)
|
60
|
+
uv pip install pytest-dsl
|
61
|
+
```
|
62
|
+
|
63
|
+
### 第二步:创建第一个测试
|
64
|
+
|
65
|
+
创建文件 `hello.dsl`:
|
66
|
+
|
67
|
+
```python
|
68
|
+
@name: "我的第一个测试"
|
69
|
+
@description: "学习pytest-dsl的第一步"
|
70
|
+
|
71
|
+
# 定义变量
|
72
|
+
message = "Hello, pytest-dsl!"
|
73
|
+
count = 3
|
74
|
+
|
75
|
+
# 打印欢迎消息
|
76
|
+
[打印], 内容: ${message}
|
77
|
+
|
78
|
+
# 简单循环
|
79
|
+
for i in range(1, ${count} + 1) do
|
80
|
+
[打印], 内容: "第 ${i} 次循环"
|
81
|
+
end
|
82
|
+
|
83
|
+
# 测试断言
|
84
|
+
[断言], 条件: "${count} == 3", 消息: "计数器应该等于3"
|
85
|
+
|
86
|
+
teardown do
|
87
|
+
[打印], 内容: "测试完成!"
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
### 第三步:运行测试
|
92
|
+
|
93
|
+
```bash
|
94
|
+
# 直接运行DSL文件
|
95
|
+
pytest-dsl hello.dsl
|
96
|
+
|
97
|
+
# 运行目录下所有DSL文件
|
98
|
+
pytest-dsl tests/
|
99
|
+
```
|
100
|
+
|
101
|
+
🎉 **恭喜!** 您已经成功运行了第一个pytest-dsl测试!
|
102
|
+
|
103
|
+
## 📚 基础教程
|
104
|
+
|
105
|
+
### 1. 基本语法入门
|
106
|
+
|
107
|
+
#### 变量和数据类型
|
108
|
+
|
109
|
+
```python
|
110
|
+
# 字符串变量
|
111
|
+
name = "pytest-dsl"
|
112
|
+
version = "1.0.0"
|
113
|
+
|
114
|
+
# 数字变量
|
115
|
+
port = 8080
|
116
|
+
|
117
|
+
# 布尔值变量
|
118
|
+
is_enabled = True
|
119
|
+
is_disabled = False
|
120
|
+
|
121
|
+
# 列表
|
122
|
+
users = ["alice", "bob", "charlie"]
|
123
|
+
|
124
|
+
# 字典
|
125
|
+
user_info = {"name": "张三", "age": 30, "city": "北京"}
|
126
|
+
|
127
|
+
# 嵌套字典
|
128
|
+
config = {
|
129
|
+
"database": {
|
130
|
+
"host": "localhost",
|
131
|
+
"port": 3306,
|
132
|
+
"name": "test_db"
|
133
|
+
},
|
134
|
+
"api": {
|
135
|
+
"base_url": "https://api.example.com",
|
136
|
+
"timeout": 30
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
# 访问字典值
|
141
|
+
username = ${user_info["name"]}
|
142
|
+
db_host = ${config["database"]["host"]}
|
143
|
+
```
|
144
|
+
|
145
|
+
#### 流程控制
|
146
|
+
|
147
|
+
```python
|
148
|
+
# 条件判断
|
149
|
+
status = "success"
|
150
|
+
if status == "success" do
|
151
|
+
[打印], 内容: "测试通过"
|
152
|
+
else
|
153
|
+
[打印], 内容: "测试失败"
|
154
|
+
end
|
155
|
+
|
156
|
+
# 使用布尔值的条件判断
|
157
|
+
is_ready = True
|
158
|
+
if ${is_ready} do
|
159
|
+
[打印], 内容: "系统就绪"
|
160
|
+
end
|
161
|
+
|
162
|
+
# 循环结构
|
163
|
+
num = 4
|
164
|
+
for i in range(1, num) do
|
165
|
+
[打印], 内容: "执行第 ${i} 次"
|
166
|
+
end
|
167
|
+
|
168
|
+
# 循环中的break和continue
|
169
|
+
for j in range(1, 11) do
|
170
|
+
# 跳过偶数
|
171
|
+
if ${j} % 2 == 0 do
|
172
|
+
continue
|
173
|
+
end
|
174
|
+
|
175
|
+
# 当达到7时退出循环
|
176
|
+
if ${j} == 7 do
|
177
|
+
[打印], 内容: "达到7,退出循环"
|
178
|
+
break
|
179
|
+
end
|
180
|
+
|
181
|
+
[打印], 内容: "奇数: ${j}"
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
### 2. 内置关键字详解
|
186
|
+
|
187
|
+
#### 基础关键字
|
188
|
+
|
189
|
+
```python
|
190
|
+
# 打印输出
|
191
|
+
[打印], 内容: "Hello World"
|
192
|
+
|
193
|
+
# 断言测试
|
194
|
+
[断言], 条件: "1 + 1 == 2", 消息: "数学计算错误"
|
195
|
+
|
196
|
+
# 等待
|
197
|
+
[等待], 秒数: 2
|
198
|
+
|
199
|
+
# 生成随机数
|
200
|
+
random_num = [生成随机数], 最小值: 1, 最大值: 100
|
201
|
+
[打印], 内容: "随机数: ${random_num}"
|
202
|
+
```
|
203
|
+
|
204
|
+
#### 变量操作
|
205
|
+
|
206
|
+
```python
|
207
|
+
[设置全局变量], 变量名: "test_env", 值: "development"
|
208
|
+
|
209
|
+
# 获取全局变量
|
210
|
+
env = [获取全局变量], 变量名: "test_env"
|
211
|
+
[打印], 内容: "当前环境: ${env}"
|
212
|
+
```
|
213
|
+
|
214
|
+
### 3. 自定义关键字(函数)
|
215
|
+
|
216
|
+
自定义关键字让您可以封装复用的测试逻辑:
|
217
|
+
|
218
|
+
```python
|
219
|
+
@name: "自定义关键字示例"
|
220
|
+
|
221
|
+
# 定义一个计算器关键字
|
222
|
+
function 计算器 (操作, 数字1, 数字2=0) do
|
223
|
+
if ${操作} == "加法" do
|
224
|
+
[打印],内容: "执行加法操作"
|
225
|
+
结果 = ${数字1} + ${数字2}
|
226
|
+
else
|
227
|
+
结果 = 12
|
228
|
+
end
|
229
|
+
|
230
|
+
[打印], 内容: "${数字1} ${操作} ${数字2} = ${结果}"
|
231
|
+
return ${结果}
|
232
|
+
end
|
233
|
+
|
234
|
+
# 使用自定义关键字
|
235
|
+
sum_result = [计算器], 操作: "加法", 数字1: 10, 数字2: 5
|
236
|
+
product_result = [计算器], 操作: "其他", 数字1: 3, 数字2: 4
|
237
|
+
|
238
|
+
# 验证结果
|
239
|
+
[断言], 条件: "${sum_result} == 15", 消息: "加法计算错误"
|
240
|
+
[断言], 条件: "${product_result} == 12", 消息: "其他计算错误"
|
241
|
+
```
|
242
|
+
|
243
|
+
#### 资源文件复用
|
244
|
+
|
245
|
+
将常用关键字保存在资源文件中(`.resource`),实现跨项目复用:
|
246
|
+
|
247
|
+
**创建资源文件 `utils.resource`:**
|
248
|
+
|
249
|
+
```python
|
250
|
+
@name: "通用工具关键字"
|
251
|
+
|
252
|
+
# 定义一个简单的关键字(函数)
|
253
|
+
function 拼接字符串 (前缀, 后缀="默认后缀") do
|
254
|
+
# 直接使用关键字参数
|
255
|
+
[打印],内容: "拼接前缀: ${前缀} 和后缀: ${后缀}"
|
256
|
+
|
257
|
+
# 保存到变量中
|
258
|
+
结果变量 = "${前缀}${后缀}"
|
259
|
+
[打印],内容: "拼接结果: ${结果变量}"
|
260
|
+
|
261
|
+
# 返回结果
|
262
|
+
return ${结果变量}
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
**在测试中使用资源文件:**
|
267
|
+
|
268
|
+
```python
|
269
|
+
@name: "使用资源文件示例"
|
270
|
+
@import: "utils.resource"
|
271
|
+
|
272
|
+
# 使用自定义关键字
|
273
|
+
问候语 = [拼接字符串],前缀: "你好, ",后缀: "世界"
|
274
|
+
|
275
|
+
# 只传递必要参数,使用默认值
|
276
|
+
简单问候 = [拼接字符串],前缀: "你好"
|
277
|
+
[打印],内容: ${简单问候} # 输出: 你好默认后缀
|
278
|
+
```
|
279
|
+
|
280
|
+
### 4. API测试入门
|
281
|
+
|
282
|
+
#### 简单的GET请求
|
283
|
+
|
284
|
+
```python
|
285
|
+
@name: "API测试入门"
|
286
|
+
@description: "学习基本的API测试方法"
|
287
|
+
|
288
|
+
# 简单的GET请求
|
289
|
+
[HTTP请求], 客户端: "default", 配置: '''
|
290
|
+
method: GET
|
291
|
+
url: https://jsonplaceholder.typicode.com/posts/1
|
292
|
+
asserts:
|
293
|
+
- ["status", "eq", 200]
|
294
|
+
- ["jsonpath", "$.title", "contains", "sunt"]
|
295
|
+
''', 步骤名称: "获取文章详情"
|
296
|
+
```
|
297
|
+
|
298
|
+
#### 带参数的请求
|
299
|
+
|
300
|
+
```python
|
301
|
+
# 带查询参数的GET请求
|
302
|
+
[HTTP请求], 客户端: "default", 配置: '''
|
303
|
+
method: GET
|
304
|
+
url: https://jsonplaceholder.typicode.com/posts
|
305
|
+
request:
|
306
|
+
params:
|
307
|
+
userId: 1
|
308
|
+
_limit: 5
|
309
|
+
asserts:
|
310
|
+
- ["status", "eq", 200]
|
311
|
+
- ["jsonpath", "$", "length", "eq", 5]
|
312
|
+
''', 步骤名称: "获取用户文章列表"
|
313
|
+
```
|
314
|
+
|
315
|
+
#### 数据捕获和变量使用
|
316
|
+
|
317
|
+
```python
|
318
|
+
# 捕获响应数据
|
319
|
+
[HTTP请求], 客户端: "default", 配置: '''
|
320
|
+
method: GET
|
321
|
+
url: https://jsonplaceholder.typicode.com/users/1
|
322
|
+
captures:
|
323
|
+
user_name: ["jsonpath", "$.name"]
|
324
|
+
user_email: ["jsonpath", "$.email"]
|
325
|
+
asserts:
|
326
|
+
- ["status", "eq", 200]
|
327
|
+
''', 步骤名称: "获取用户信息"
|
328
|
+
|
329
|
+
# 使用捕获的变量
|
330
|
+
[打印], 内容: "用户名: ${user_name}"
|
331
|
+
[打印], 内容: "邮箱: ${user_email}"
|
332
|
+
|
333
|
+
# 在后续请求中使用
|
334
|
+
[HTTP请求], 客户端: "default", 配置: '''
|
335
|
+
method: GET
|
336
|
+
url: https://jsonplaceholder.typicode.com/posts
|
337
|
+
request:
|
338
|
+
params:
|
339
|
+
userId: 1
|
340
|
+
asserts:
|
341
|
+
- ["status", "eq", 200]
|
342
|
+
''', 步骤名称: "根据用户ID获取文章"
|
343
|
+
```
|
344
|
+
|
345
|
+
## 🚀 进阶功能
|
346
|
+
|
347
|
+
### 1. 环境配置管理
|
348
|
+
|
349
|
+
#### YAML变量文件
|
350
|
+
|
351
|
+
创建 `config/dev.yaml` 管理开发环境配置:
|
352
|
+
|
353
|
+
```yaml
|
354
|
+
# 环境配置
|
355
|
+
environment: "development"
|
356
|
+
debug: true
|
357
|
+
|
358
|
+
# API配置
|
359
|
+
api:
|
360
|
+
base_url: "https://jsonplaceholder.typicode.com"
|
361
|
+
timeout: 30
|
362
|
+
retry_count: 3
|
363
|
+
|
364
|
+
# HTTP客户端配置
|
365
|
+
http_clients:
|
366
|
+
default:
|
367
|
+
base_url: "${api.base_url}"
|
368
|
+
timeout: ${api.timeout}
|
369
|
+
headers:
|
370
|
+
Content-Type: "application/json"
|
371
|
+
User-Agent: "pytest-dsl/1.0"
|
372
|
+
|
373
|
+
# 测试数据
|
374
|
+
test_users:
|
375
|
+
admin:
|
376
|
+
username: "admin"
|
377
|
+
password: "admin123"
|
378
|
+
normal:
|
379
|
+
username: "user"
|
380
|
+
password: "user123"
|
381
|
+
|
382
|
+
# 数据库配置
|
383
|
+
database:
|
384
|
+
host: "localhost"
|
385
|
+
port: 5432
|
386
|
+
name: "test_db"
|
387
|
+
```
|
388
|
+
|
389
|
+
#### 使用配置文件
|
390
|
+
|
391
|
+
```bash
|
392
|
+
# 运行时指定配置文件
|
393
|
+
pytest-dsl tests/ --yaml-vars config/dev.yaml
|
394
|
+
```
|
395
|
+
|
396
|
+
#### 在DSL中使用配置
|
397
|
+
|
398
|
+
```python
|
399
|
+
@name: "使用环境配置"
|
400
|
+
|
401
|
+
# 直接使用YAML中的变量
|
402
|
+
[打印], 内容: "当前环境: ${environment}"
|
403
|
+
[打印], 内容: "API地址: ${api.base_url}"
|
404
|
+
|
405
|
+
# 使用嵌套配置(支持增强的变量访问语法)
|
406
|
+
admin_user = ${test_users.admin.username}
|
407
|
+
admin_pass = ${test_users.admin.password}
|
408
|
+
|
409
|
+
# 支持数组索引访问
|
410
|
+
first_user = ${users_array[0].name}
|
411
|
+
last_user = ${users_array[-1].name}
|
412
|
+
|
413
|
+
# 支持字典键访问
|
414
|
+
api_server = ${config_map["api-server"]}
|
415
|
+
timeout_config = ${config_map['timeout']}
|
416
|
+
|
417
|
+
[HTTP请求], 客户端: "default", 配置: '''
|
418
|
+
method: POST
|
419
|
+
url: ${api.base_url}/auth/login
|
420
|
+
request:
|
421
|
+
json:
|
422
|
+
username: "${admin_user}"
|
423
|
+
password: "${admin_pass}"
|
424
|
+
asserts:
|
425
|
+
- ["status", "eq", 200]
|
426
|
+
''', 步骤名称: "管理员登录"
|
427
|
+
```
|
428
|
+
|
429
|
+
### 2. 增强的变量访问语法
|
430
|
+
|
431
|
+
pytest-dsl 支持类似 Python 的强大变量访问语法:
|
432
|
+
|
433
|
+
#### 支持的语法类型
|
434
|
+
|
435
|
+
```python
|
436
|
+
# 基本变量访问
|
437
|
+
${variable_name}
|
438
|
+
|
439
|
+
# 点号访问(对象属性)
|
440
|
+
${object.property}
|
441
|
+
${nested.object.property}
|
442
|
+
|
443
|
+
# 数组索引访问
|
444
|
+
${array[0]} # 第一个元素
|
445
|
+
${array[-1]} # 最后一个元素
|
446
|
+
|
447
|
+
# 字典键访问
|
448
|
+
${dict["key"]} # 使用双引号
|
449
|
+
${dict['key']} # 使用单引号
|
450
|
+
|
451
|
+
# 混合访问模式
|
452
|
+
${users[0].name} # 数组中对象的属性
|
453
|
+
${data["users"][0]["name"]} # 嵌套字典和数组
|
454
|
+
${config.servers[0].endpoints["api"]} # 复杂嵌套结构
|
455
|
+
```
|
456
|
+
|
457
|
+
#### 实际使用示例
|
458
|
+
|
459
|
+
```yaml
|
460
|
+
# YAML配置文件
|
461
|
+
users:
|
462
|
+
- id: 1
|
463
|
+
name: "张三"
|
464
|
+
roles: ["admin", "user"]
|
465
|
+
profile:
|
466
|
+
email: "zhangsan@example.com"
|
467
|
+
settings:
|
468
|
+
theme: "dark"
|
469
|
+
|
470
|
+
config:
|
471
|
+
"api-server": "https://api.example.com"
|
472
|
+
"timeout": 30
|
473
|
+
```
|
474
|
+
|
475
|
+
```python
|
476
|
+
# DSL测试文件
|
477
|
+
@name: "变量访问语法示例"
|
478
|
+
|
479
|
+
# 数组访问
|
480
|
+
first_user = ${users[0].name}
|
481
|
+
[打印], 内容: "第一个用户: ${first_user}"
|
482
|
+
|
483
|
+
# 嵌套访问
|
484
|
+
user_theme = ${users[0].profile.settings.theme}
|
485
|
+
[打印], 内容: "用户主题: ${user_theme}"
|
486
|
+
|
487
|
+
# 字典键访问
|
488
|
+
api_server = ${config["api-server"]}
|
489
|
+
[打印], 内容: "API服务器: ${api_server}"
|
490
|
+
|
491
|
+
# 在字符串中使用
|
492
|
+
[打印], 内容: "用户${users[0].name}的角色是${users[0].roles[0]}"
|
493
|
+
```
|
494
|
+
|
495
|
+
详细文档请参考:[增强的变量访问语法](docs/enhanced_variable_access.md)
|
496
|
+
|
497
|
+
### 3. 数据驱动测试
|
498
|
+
|
499
|
+
#### CSV数据驱动
|
500
|
+
|
501
|
+
注意:这种数据驱动模式只有用pytest命令运行的时候才可以
|
502
|
+
|
503
|
+
创建 `test_data.csv`:
|
504
|
+
|
505
|
+
```csv
|
506
|
+
username,password,expected_status,test_case
|
507
|
+
admin,admin123,200,管理员登录成功
|
508
|
+
user,user123,200,普通用户登录成功
|
509
|
+
invalid,wrong,401,错误密码登录失败
|
510
|
+
"",admin123,400,空用户名登录失败
|
511
|
+
```
|
512
|
+
|
513
|
+
使用CSV数据:
|
514
|
+
|
515
|
+
```python
|
516
|
+
@name: "登录功能测试"
|
517
|
+
@data: "test_data.csv" using csv
|
518
|
+
@description: "使用CSV数据测试登录功能"
|
519
|
+
|
520
|
+
# CSV中的每一行都会执行一次这个测试
|
521
|
+
[打印], 内容: "测试用例: ${test_case}"
|
522
|
+
[打印], 内容: "用户名: ${username}, 密码: ${password}, 期望状态: ${expected_status}"
|
523
|
+
|
524
|
+
# 模拟HTTP请求(实际应该是真实的API调用)
|
525
|
+
[打印], 内容: "模拟登录请求..."
|
526
|
+
|
527
|
+
# 简单的条件判断来模拟不同的测试结果
|
528
|
+
if "${username}" == "admin" do
|
529
|
+
[打印], 内容: "管理员登录测试"
|
530
|
+
else
|
531
|
+
[打印], 内容: "无效用户登录测试"
|
532
|
+
end
|
533
|
+
|
534
|
+
[打印], 内容: "测试用例: ${test_case} - 完成"
|
535
|
+
```
|
536
|
+
|
537
|
+
### 3. 断言重试机制
|
538
|
+
|
539
|
+
对于异步API或需要等待的场景:
|
540
|
+
|
541
|
+
```python
|
542
|
+
# 带重试的断言
|
543
|
+
[HTTP请求], 客户端: "default", 配置: '''
|
544
|
+
method: GET
|
545
|
+
url: https://jsonplaceholder.typicode.com/posts/1
|
546
|
+
asserts:
|
547
|
+
- ["status", "eq", 200]
|
548
|
+
- ["jsonpath", "$.id", "eq", 1]
|
549
|
+
''', 断言重试次数: 3, 断言重试间隔: 1, 步骤名称: "测试断言重试机制"
|
550
|
+
```
|
551
|
+
|
552
|
+
### 4. 远程关键字功能
|
553
|
+
|
554
|
+
pytest-dsl支持分布式测试,可以在不同机器上执行关键字:
|
555
|
+
|
556
|
+
#### 启动远程服务
|
557
|
+
|
558
|
+
```bash
|
559
|
+
# 在远程机器上启动关键字服务
|
560
|
+
pytest-dsl-server --host 0.0.0.0 --port 8270
|
561
|
+
|
562
|
+
# 带API密钥的安全启动
|
563
|
+
pytest-dsl-server --host 0.0.0.0 --port 8270 --api-key your_secret_key
|
564
|
+
```
|
565
|
+
|
566
|
+
#### 使用远程关键字
|
567
|
+
|
568
|
+
**方式一:DSL中直接连接**
|
569
|
+
|
570
|
+
```python
|
571
|
+
@name: "远程关键字测试"
|
572
|
+
@remote: "http://remote-server:8270/" as remote_machine
|
573
|
+
|
574
|
+
# 在远程机器上执行关键字
|
575
|
+
remote_machine|[打印], 内容: "这在远程机器上执行"
|
576
|
+
result = remote_machine|[生成随机数], 最小值: 1, 最大值: 100
|
577
|
+
[打印], 内容: "远程生成的随机数: ${result}"
|
578
|
+
```
|
579
|
+
|
580
|
+
**方式二:YAML配置自动加载(推荐)**
|
581
|
+
|
582
|
+
在 `config/vars.yaml` 中配置:
|
583
|
+
|
584
|
+
```yaml
|
585
|
+
remote_servers:
|
586
|
+
main_server:
|
587
|
+
url: "http://server1:8270/"
|
588
|
+
alias: "server1"
|
589
|
+
api_key: "your_api_key"
|
590
|
+
sync_config:
|
591
|
+
sync_global_vars: true
|
592
|
+
sync_yaml_vars: true
|
593
|
+
|
594
|
+
backup_server:
|
595
|
+
url: "http://server2:8270/"
|
596
|
+
alias: "server2"
|
597
|
+
```
|
598
|
+
|
599
|
+
然后直接使用:
|
600
|
+
|
601
|
+
```python
|
602
|
+
# 无需@remote导入,直接使用
|
603
|
+
server1|[HTTP请求], 客户端: "default", 配置: '''
|
604
|
+
method: GET
|
605
|
+
url: https://jsonplaceholder.typicode.com/posts/1
|
606
|
+
'''
|
607
|
+
|
608
|
+
server2|[打印], 内容: "备用服务器执行"
|
609
|
+
```
|
610
|
+
|
611
|
+
#### 无缝变量传递
|
612
|
+
|
613
|
+
客户端的变量会自动传递到远程服务器:
|
614
|
+
|
615
|
+
```python
|
616
|
+
# 客户端定义的变量
|
617
|
+
api_url = "https://jsonplaceholder.typicode.com"
|
618
|
+
user_token = "abc123"
|
619
|
+
|
620
|
+
# 远程服务器可以直接使用这些变量
|
621
|
+
remote_machine|[HTTP请求], 客户端: "default", 配置: '''
|
622
|
+
method: GET
|
623
|
+
url: ${api_url}/users/1
|
624
|
+
request:
|
625
|
+
headers:
|
626
|
+
Authorization: "Bearer ${user_token}"
|
627
|
+
'''
|
628
|
+
```
|
629
|
+
|
630
|
+
## 📋 实战案例
|
631
|
+
|
632
|
+
### 完整的API测试项目
|
633
|
+
|
634
|
+
让我们创建一个完整的API测试项目来演示pytest-dsl的强大功能:
|
635
|
+
|
636
|
+
#### 项目结构
|
637
|
+
|
638
|
+
```
|
639
|
+
my-api-tests/
|
640
|
+
├── config/
|
641
|
+
│ ├── dev.yaml # 开发环境配置
|
642
|
+
│ ├── prod.yaml # 生产环境配置
|
643
|
+
│ └── base.yaml # 基础配置
|
644
|
+
├── resources/
|
645
|
+
│ ├── auth.resource # 认证相关关键字
|
646
|
+
│ └── utils.resource # 工具关键字
|
647
|
+
├── tests/
|
648
|
+
│ ├── auth/
|
649
|
+
│ │ ├── login.dsl # 登录测试
|
650
|
+
│ │ └── logout.dsl # 登出测试
|
651
|
+
│ ├── users/
|
652
|
+
│ │ ├── create_user.dsl
|
653
|
+
│ │ └── get_user.dsl
|
654
|
+
│ └── data/
|
655
|
+
│ └── users.csv # 测试数据
|
656
|
+
├── test_runner.py # pytest集成
|
657
|
+
└── pytest.ini # pytest配置
|
658
|
+
```
|
659
|
+
|
660
|
+
#### 基础配置 `config/base.yaml`
|
661
|
+
|
662
|
+
```yaml
|
663
|
+
# 通用配置
|
664
|
+
app_name: "My API"
|
665
|
+
version: "1.0.0"
|
666
|
+
|
667
|
+
# HTTP客户端配置
|
668
|
+
http_clients:
|
669
|
+
default:
|
670
|
+
timeout: 30
|
671
|
+
headers:
|
672
|
+
Content-Type: "application/json"
|
673
|
+
User-Agent: "${app_name}/${version}"
|
674
|
+
```
|
675
|
+
|
676
|
+
#### 开发环境配置 `config/dev.yaml`
|
677
|
+
|
678
|
+
```yaml
|
679
|
+
# 继承基础配置
|
680
|
+
extends: "base.yaml"
|
681
|
+
|
682
|
+
# 开发环境特定配置
|
683
|
+
environment: "development"
|
684
|
+
debug: true
|
685
|
+
|
686
|
+
api:
|
687
|
+
base_url: "https://jsonplaceholder.typicode.com"
|
688
|
+
|
689
|
+
# 测试用户
|
690
|
+
test_users:
|
691
|
+
admin:
|
692
|
+
username: "admin"
|
693
|
+
password: "admin123"
|
694
|
+
normal:
|
695
|
+
username: "testuser"
|
696
|
+
password: "test123"
|
697
|
+
|
698
|
+
# 数据库配置
|
699
|
+
database:
|
700
|
+
host: "localhost"
|
701
|
+
port: 5432
|
702
|
+
name: "test_db"
|
703
|
+
```
|
704
|
+
|
705
|
+
#### 认证关键字 `resources/auth.resource`
|
706
|
+
|
707
|
+
```python
|
708
|
+
@name: "认证相关关键字"
|
709
|
+
@description: "处理登录、登出等认证操作"
|
710
|
+
|
711
|
+
function 用户登录 (用户名, 密码, 客户端="default") do
|
712
|
+
[打印], 内容: "模拟用户登录: ${用户名}"
|
713
|
+
|
714
|
+
# 模拟HTTP登录请求
|
715
|
+
[HTTP请求], 客户端: ${客户端}, 配置: '''
|
716
|
+
method: GET
|
717
|
+
url: https://jsonplaceholder.typicode.com/users/1
|
718
|
+
captures:
|
719
|
+
access_token: ["jsonpath", "$.id"]
|
720
|
+
user_id: ["jsonpath", "$.id"]
|
721
|
+
asserts:
|
722
|
+
- ["status", "eq", 200]
|
723
|
+
''', 步骤名称: "用户登录: ${用户名}"
|
724
|
+
|
725
|
+
# 设置全局token供后续请求使用
|
726
|
+
[设置全局变量], 变量名: "auth_token", 值: ${access_token}
|
727
|
+
[设置全局变量], 变量名: "current_user_id", 值: ${user_id}
|
728
|
+
|
729
|
+
return ${access_token}
|
730
|
+
end
|
731
|
+
|
732
|
+
function 用户登出 (客户端="default") do
|
733
|
+
token = [获取全局变量], 变量名: "auth_token"
|
734
|
+
[打印], 内容: "模拟用户登出,token: ${token}"
|
735
|
+
|
736
|
+
# 模拟HTTP登出请求
|
737
|
+
[HTTP请求], 客户端: ${客户端}, 配置: '''
|
738
|
+
method: GET
|
739
|
+
url: https://jsonplaceholder.typicode.com/posts/1
|
740
|
+
asserts:
|
741
|
+
- ["status", "eq", 200]
|
742
|
+
''', 步骤名称: "用户登出"
|
743
|
+
|
744
|
+
# 清除认证信息
|
745
|
+
[设置全局变量], 变量名: "auth_token", 值: ""
|
746
|
+
[设置全局变量], 变量名: "current_user_id", 值: ""
|
747
|
+
end
|
748
|
+
```
|
749
|
+
|
750
|
+
#### 登录测试 `tests/auth/login.dsl`
|
751
|
+
|
752
|
+
```python
|
753
|
+
@name: "用户登录功能测试"
|
754
|
+
@description: "测试用户登录的各种场景"
|
755
|
+
@tags: ["auth", "login"]
|
756
|
+
@import: "resources/auth.resource"
|
757
|
+
|
758
|
+
# 测试管理员登录
|
759
|
+
admin_token = [用户登录], 用户名: ${test_users.admin.username}, 密码: ${test_users.admin.password}
|
760
|
+
[断言], 条件: "${admin_token} != ''", 消息: "管理员登录失败"
|
761
|
+
|
762
|
+
# 验证登录状态(模拟)
|
763
|
+
[HTTP请求], 客户端: "default", 配置: '''
|
764
|
+
method: GET
|
765
|
+
url: https://jsonplaceholder.typicode.com/users/1
|
766
|
+
asserts:
|
767
|
+
- ["status", "eq", 200]
|
768
|
+
- ["jsonpath", "$.name", "exists"]
|
769
|
+
''', 步骤名称: "验证登录状态"
|
770
|
+
|
771
|
+
# 测试登出
|
772
|
+
[用户登出]
|
773
|
+
|
774
|
+
teardown do
|
775
|
+
[打印], 内容: "登录测试完成"
|
776
|
+
end
|
777
|
+
```
|
778
|
+
|
779
|
+
#### pytest集成 `test_runner.py`
|
780
|
+
|
781
|
+
```python
|
782
|
+
from pytest_dsl.core.auto_decorator import auto_dsl
|
783
|
+
|
784
|
+
@auto_dsl("./tests")
|
785
|
+
class TestAPI:
|
786
|
+
"""API自动化测试套件
|
787
|
+
|
788
|
+
自动加载tests目录下的所有DSL文件
|
789
|
+
"""
|
790
|
+
pass
|
791
|
+
|
792
|
+
@auto_dsl("./tests/auth")
|
793
|
+
class TestAuth:
|
794
|
+
"""认证模块测试"""
|
795
|
+
pass
|
796
|
+
|
797
|
+
@auto_dsl("./tests/users")
|
798
|
+
class TestUsers:
|
799
|
+
"""用户模块测试"""
|
800
|
+
pass
|
801
|
+
```
|
802
|
+
|
803
|
+
#### 运行测试
|
804
|
+
|
805
|
+
```bash
|
806
|
+
# 使用开发环境配置运行所有测试
|
807
|
+
pytest-dsl tests/ --yaml-vars config/dev.yaml
|
808
|
+
|
809
|
+
# 使用pytest运行(支持更多选项)
|
810
|
+
pytest test_runner.py --yaml-vars config/dev.yaml -v
|
811
|
+
|
812
|
+
# 生成Allure报告
|
813
|
+
pytest test_runner.py --yaml-vars config/dev.yaml --alluredir=reports
|
814
|
+
allure serve reports
|
815
|
+
```
|
816
|
+
|
817
|
+
## 🔧 扩展开发
|
818
|
+
|
819
|
+
### 创建自定义关键字
|
820
|
+
|
821
|
+
pytest-dsl的强大之处在于可以轻松扩展自定义关键字:
|
822
|
+
|
823
|
+
#### 基础关键字开发
|
824
|
+
|
825
|
+
```python
|
826
|
+
# keywords/my_keywords.py
|
827
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
828
|
+
|
829
|
+
@keyword_manager.register('数据库查询', [
|
830
|
+
{'name': '查询语句', 'mapping': 'sql', 'description': 'SQL查询语句'},
|
831
|
+
{'name': '数据库', 'mapping': 'database', 'description': '数据库连接名', 'default': 'default'}
|
832
|
+
])
|
833
|
+
def database_query(**kwargs):
|
834
|
+
"""执行数据库查询"""
|
835
|
+
sql = kwargs.get('sql')
|
836
|
+
database = kwargs.get('database', 'default')
|
837
|
+
context = kwargs.get('context')
|
838
|
+
|
839
|
+
# 从上下文获取数据库配置
|
840
|
+
db_config = context.get_variable('database')
|
841
|
+
|
842
|
+
# 实现数据库查询逻辑
|
843
|
+
# connection = create_connection(db_config)
|
844
|
+
# result = connection.execute(sql)
|
845
|
+
|
846
|
+
# 模拟查询结果
|
847
|
+
result = [{"id": 1, "name": "test"}]
|
848
|
+
|
849
|
+
return result
|
850
|
+
|
851
|
+
@keyword_manager.register('发送邮件', [
|
852
|
+
{'name': '收件人', 'mapping': 'to_email', 'description': '收件人邮箱'},
|
853
|
+
{'name': '主题', 'mapping': 'subject', 'description': '邮件主题'},
|
854
|
+
{'name': '内容', 'mapping': 'content', 'description': '邮件内容'}
|
855
|
+
])
|
856
|
+
def send_email(**kwargs):
|
857
|
+
"""发送邮件通知"""
|
858
|
+
to_email = kwargs.get('to_email')
|
859
|
+
subject = kwargs.get('subject')
|
860
|
+
content = kwargs.get('content')
|
861
|
+
|
862
|
+
# 实现邮件发送逻辑
|
863
|
+
print(f"发送邮件到 {to_email}: {subject}")
|
864
|
+
|
865
|
+
return True
|
866
|
+
```
|
867
|
+
|
868
|
+
#### 在DSL中使用自定义关键字
|
869
|
+
|
870
|
+
```python
|
871
|
+
@name: "使用自定义关键字测试"
|
872
|
+
|
873
|
+
# 使用数据库查询关键字
|
874
|
+
users = [数据库查询], 查询语句: "SELECT * FROM users WHERE active = 1"
|
875
|
+
[打印], 内容: "查询到 ${len(users)} 个活跃用户"
|
876
|
+
|
877
|
+
# 发送测试报告邮件
|
878
|
+
[发送邮件], 收件人: "admin@example.com", 主题: "测试报告", 内容: "测试已完成"
|
879
|
+
```
|
880
|
+
|
881
|
+
#### 支持远程模式的关键字
|
882
|
+
|
883
|
+
```python
|
884
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
885
|
+
|
886
|
+
@keyword_manager.register('文件操作', [
|
887
|
+
{'name': '操作类型', 'mapping': 'operation', 'description': '操作类型:read/write/delete'},
|
888
|
+
{'name': '文件路径', 'mapping': 'file_path', 'description': '文件路径'},
|
889
|
+
{'name': '内容', 'mapping': 'content', 'description': '文件内容(写入时使用)', 'default': ''}
|
890
|
+
])
|
891
|
+
def file_operation(**kwargs):
|
892
|
+
"""文件操作关键字,支持远程执行"""
|
893
|
+
operation = kwargs.get('operation')
|
894
|
+
file_path = kwargs.get('file_path')
|
895
|
+
content = kwargs.get('content', '')
|
896
|
+
|
897
|
+
if operation == 'read':
|
898
|
+
# 读取文件
|
899
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
900
|
+
return f.read()
|
901
|
+
elif operation == 'write':
|
902
|
+
# 写入文件
|
903
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
904
|
+
f.write(content)
|
905
|
+
return True
|
906
|
+
elif operation == 'delete':
|
907
|
+
# 删除文件
|
908
|
+
import os
|
909
|
+
os.remove(file_path)
|
910
|
+
return True
|
911
|
+
|
912
|
+
return False
|
913
|
+
```
|
914
|
+
|
915
|
+
### 关键字开发最佳实践
|
916
|
+
|
917
|
+
1. **参数验证**:始终验证输入参数
|
918
|
+
2. **错误处理**:提供清晰的错误信息
|
919
|
+
3. **文档说明**:为每个关键字提供详细的文档
|
920
|
+
4. **返回值**:确保关键字有明确的返回值
|
921
|
+
5. **上下文使用**:合理使用context获取全局变量
|
922
|
+
|
923
|
+
## 🚀 部署运维
|
924
|
+
|
925
|
+
### 与pytest集成
|
926
|
+
|
927
|
+
```python
|
928
|
+
# test_runner.py
|
929
|
+
from pytest_dsl.core.auto_decorator import auto_dsl
|
930
|
+
|
931
|
+
@auto_dsl("./tests")
|
932
|
+
class TestAPI:
|
933
|
+
"""自动加载tests目录下的所有DSL文件"""
|
934
|
+
pass
|
935
|
+
```
|
936
|
+
|
937
|
+
### 生成测试报告
|
938
|
+
|
939
|
+
```bash
|
940
|
+
# 生成Allure报告
|
941
|
+
pytest test_runner.py --alluredir=reports
|
942
|
+
allure serve reports
|
943
|
+
|
944
|
+
# 生成HTML报告
|
945
|
+
pytest test_runner.py --html=report.html --self-contained-html
|
946
|
+
```
|
947
|
+
|
948
|
+
### CI/CD集成
|
949
|
+
|
950
|
+
#### GitHub Actions示例
|
951
|
+
|
952
|
+
```yaml
|
953
|
+
# .github/workflows/test.yml
|
954
|
+
name: API Tests
|
955
|
+
|
956
|
+
on: [push, pull_request]
|
957
|
+
|
958
|
+
jobs:
|
959
|
+
test:
|
960
|
+
runs-on: ubuntu-latest
|
961
|
+
|
962
|
+
steps:
|
963
|
+
- uses: actions/checkout@v2
|
964
|
+
|
965
|
+
- name: Set up Python
|
966
|
+
uses: actions/setup-python@v2
|
967
|
+
with:
|
968
|
+
python-version: '3.9'
|
969
|
+
|
970
|
+
- name: Install dependencies
|
971
|
+
run: |
|
972
|
+
pip install pytest-dsl allure-pytest
|
973
|
+
|
974
|
+
- name: Run tests
|
975
|
+
run: |
|
976
|
+
pytest-dsl tests/ --yaml-vars config/ci.yaml --alluredir=allure-results
|
977
|
+
|
978
|
+
- name: Generate report
|
979
|
+
uses: simple-elf/allure-report-action@master
|
980
|
+
if: always()
|
981
|
+
with:
|
982
|
+
allure_results: allure-results
|
983
|
+
allure_history: allure-history
|
984
|
+
```
|
985
|
+
|
986
|
+
### Docker部署
|
987
|
+
|
988
|
+
```dockerfile
|
989
|
+
# Dockerfile
|
990
|
+
FROM python:3.9-slim
|
991
|
+
|
992
|
+
WORKDIR /app
|
993
|
+
|
994
|
+
COPY requirements.txt .
|
995
|
+
RUN pip install -r requirements.txt
|
996
|
+
|
997
|
+
COPY . .
|
998
|
+
|
999
|
+
CMD ["pytest-dsl", "tests/", "--yaml-vars", "config/prod.yaml"]
|
1000
|
+
```
|
1001
|
+
|
1002
|
+
## 📖 参考文档
|
1003
|
+
|
1004
|
+
### 核心功能文档
|
1005
|
+
- [完整DSL语法指南](./docs/自动化关键字DSL语法设计.md)
|
1006
|
+
- [HTTP测试关键字详解](./docs/api.md)
|
1007
|
+
- [断言关键字使用指南](./docs/assertion_keywords.md)
|
1008
|
+
- [HTTP断言重试机制](./docs/http_assertion_retry.md)
|
1009
|
+
|
1010
|
+
### 远程关键字文档
|
1011
|
+
- 📖 [远程关键字使用指南](./docs/remote-keywords-usage.md)
|
1012
|
+
- 🛠️ [远程关键字开发指南](./docs/remote-keywords-development.md)
|
1013
|
+
- 🔧 [远程服务器Hook机制](./docs/remote-hooks-guide.md)
|
1014
|
+
- ⚙️ [YAML远程服务器配置](./docs/yaml_remote_servers.md)
|
1015
|
+
- 🔄 [变量无缝传递功能](./docs/yaml_vars_seamless_sync.md)
|
1016
|
+
|
1017
|
+
### 示例和最佳实践
|
1018
|
+
- [远程关键字验证示例](./examples/remote/)
|
1019
|
+
- [配置文件示例](./examples/config/)
|
1020
|
+
|
1021
|
+
## 🎯 为什么选择pytest-dsl?
|
1022
|
+
|
1023
|
+
### 核心优势
|
1024
|
+
|
1025
|
+
- **🎯 零门槛上手** - 自然语言风格,测试人员无需编程基础
|
1026
|
+
- **🔧 高度可扩展** - 轻松创建自定义关键字,适应任何测试场景
|
1027
|
+
- **🌐 分布式支持** - 内置远程关键字功能,支持大规模分布式测试
|
1028
|
+
- **🔄 无缝集成** - 完美兼容pytest生态,可渐进式迁移
|
1029
|
+
- **📊 丰富报告** - 集成Allure,提供专业级测试报告
|
1030
|
+
- **🛡️ 企业级特性** - 支持环境隔离、变量管理、安全认证
|
1031
|
+
|
1032
|
+
### 适用场景
|
1033
|
+
|
1034
|
+
- **API接口测试** - 完整的HTTP测试支持
|
1035
|
+
- **微服务测试** - 分布式测试能力
|
1036
|
+
- **回归测试** - 数据驱动和批量执行
|
1037
|
+
- **集成测试** - 跨系统测试协调
|
1038
|
+
- **性能测试** - 结合其他工具进行性能测试
|
1039
|
+
|
1040
|
+
## 🤝 贡献与支持
|
1041
|
+
|
1042
|
+
我们欢迎您的贡献和反馈!
|
1043
|
+
|
1044
|
+
- 🐛 [报告问题](https://github.com/your-repo/pytest-dsl/issues)
|
1045
|
+
- 💡 [功能建议](https://github.com/your-repo/pytest-dsl/discussions)
|
1046
|
+
- 🔧 [提交PR](https://github.com/your-repo/pytest-dsl/pulls)
|
1047
|
+
|
1048
|
+
## 📄 许可证
|
1049
|
+
|
1050
|
+
MIT License - 详见 [LICENSE](LICENSE) 文件
|
1051
|
+
|
1052
|
+
---
|
1053
|
+
|
1054
|
+
## 📋 示例验证
|
1055
|
+
|
1056
|
+
本README.md中的大部分示例都已经过验证,确保可以正常运行。验证示例位于 `examples/readme_validation/` 目录中。
|
1057
|
+
|
1058
|
+
### 运行验证
|
1059
|
+
|
1060
|
+
```bash
|
1061
|
+
# 进入验证目录
|
1062
|
+
cd examples/readme_validation
|
1063
|
+
|
1064
|
+
# 运行所有验证示例
|
1065
|
+
python run_all_tests.py
|
1066
|
+
|
1067
|
+
# 或者运行单个示例
|
1068
|
+
pytest-dsl hello.dsl
|
1069
|
+
pytest-dsl api_basic.dsl
|
1070
|
+
```
|
1071
|
+
|
1072
|
+
### 验证覆盖
|
1073
|
+
|
1074
|
+
- ✅ 基础语法和内置关键字
|
1075
|
+
- ✅ 自定义关键字和资源文件
|
1076
|
+
- ✅ API测试功能
|
1077
|
+
- ✅ YAML配置管理
|
1078
|
+
- ✅ 变量访问语法
|
1079
|
+
- ✅ 断言重试机制
|
1080
|
+
- ✅ 认证功能示例
|
1081
|
+
- ✅ 数据驱动测试(pytest集成)
|
1082
|
+
- ✅ 布尔值支持和条件判断
|
1083
|
+
- ✅ 字典定义和嵌套访问
|
1084
|
+
- ✅ 循环控制语句(break/continue)
|
1085
|
+
|
1086
|
+
---
|
1087
|
+
|
1088
|
+
🚀 **开始使用pytest-dsl,让测试自动化变得简单而强大!**
|