pytest-dsl 0.9.0__tar.gz → 0.10.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.9.0/pytest_dsl.egg-info → pytest_dsl-0.10.0}/PKG-INFO +88 -23
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/README.md +87 -22
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pyproject.toml +7 -2
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/cli.py +88 -12
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/keyword_manager.py +21 -5
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/keywords/assertion_keywords.py +18 -12
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/keywords/http_keywords.py +4 -4
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/keywords/system_keywords.py +73 -48
- pytest_dsl-0.10.0/pytest_dsl/remote/__init__.py +7 -0
- pytest_dsl-0.10.0/pytest_dsl/remote/hook_manager.py +155 -0
- pytest_dsl-0.10.0/pytest_dsl/remote/keyword_client.py +399 -0
- pytest_dsl-0.10.0/pytest_dsl/remote/keyword_server.py +618 -0
- pytest_dsl-0.10.0/pytest_dsl/remote/variable_bridge.py +164 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0/pytest_dsl.egg-info}/PKG-INFO +88 -23
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl.egg-info/SOURCES.txt +5 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl.egg-info/entry_points.txt +1 -1
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/LICENSE +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/MANIFEST.in +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/__init__.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/conftest_adapter.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/__init__.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/auth_provider.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/auto_decorator.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/auto_directory.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/context.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/custom_keyword_manager.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/dsl_executor.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/dsl_executor_utils.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/global_context.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/http_client.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/http_request.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/lexer.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/parser.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/parsetab.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/plugin_discovery.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/utils.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/variable_utils.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/yaml_loader.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/core/yaml_vars.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/docs/custom_keywords.md +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/__init__.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/assert/assertion_example.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/assert/boolean_test.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/assert/expression_test.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/custom/test_advanced_keywords.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/custom/test_custom_keywords.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/custom/test_default_values.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/__init__.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/builtin_auth_test.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/file_reference_test.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/http_advanced.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/http_example.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/http_length_test.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/http_retry_assertions.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/http_retry_assertions_enhanced.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/http_with_yaml.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/new_retry_test.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/retry_assertions_only.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/retry_config_only.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/retry_debug.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/retry_with_fix.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/simple_retry.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/http/vars.yaml +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/quickstart/api_basics.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/quickstart/assertions.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/quickstart/loops.auto +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/test_assert.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/test_custom_keyword.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/test_http.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/examples/test_quickstart.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/keywords/__init__.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/keywords/global_keywords.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/main_adapter.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl/plugin.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl.egg-info/dependency_links.txt +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl.egg-info/requires.txt +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/pytest_dsl.egg-info/top_level.txt +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/setup.cfg +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/setup.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/tests/test_end_to_end_seamless.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/tests/test_enhanced_variable_access.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/tests/test_http_assertions_extractors.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/tests/test_seamless_variable_sync.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/tests/test_variable_sync.py +0 -0
- {pytest_dsl-0.9.0 → pytest_dsl-0.10.0}/tests/test_variable_sync_demo.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pytest-dsl
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.10.0
|
4
4
|
Summary: A DSL testing framework based on pytest
|
5
5
|
Author: Chen Shuanglin
|
6
6
|
License: MIT
|
@@ -42,11 +42,12 @@ pytest-dsl是一个革命性的关键字驱动测试框架,基于pytest构建
|
|
42
42
|
## ✨ 核心特性
|
43
43
|
|
44
44
|
- 🎯 **门槛上手低** - 自然语言风格,只需少量编程基础
|
45
|
-
- 🔧 **高度可扩展** -
|
45
|
+
- 🔧 **高度可扩展** - 轻松创建自定义关键字,支持参数默认值
|
46
46
|
- 🌐 **分布式执行** - 支持远程关键字调用
|
47
47
|
- 🔄 **无缝集成** - 完美兼容pytest生态
|
48
48
|
- 📊 **丰富报告** - 集成Allure测试报告
|
49
49
|
- 🛡️ **企业级** - 支持变量管理、环境隔离
|
50
|
+
- ⚡ **智能简化** - 参数默认值让DSL更加简洁易读
|
50
51
|
|
51
52
|
## 🚀 5分钟快速开始
|
52
53
|
|
@@ -850,17 +851,19 @@ def database_query(**kwargs):
|
|
850
851
|
|
851
852
|
@keyword_manager.register('发送邮件', [
|
852
853
|
{'name': '收件人', 'mapping': 'to_email', 'description': '收件人邮箱'},
|
853
|
-
{'name': '主题', 'mapping': 'subject', 'description': '邮件主题'},
|
854
|
-
{'name': '内容', 'mapping': 'content', 'description': '邮件内容'}
|
854
|
+
{'name': '主题', 'mapping': 'subject', 'description': '邮件主题', 'default': '测试邮件'},
|
855
|
+
{'name': '内容', 'mapping': 'content', 'description': '邮件内容', 'default': '这是一封测试邮件'},
|
856
|
+
{'name': '优先级', 'mapping': 'priority', 'description': '邮件优先级', 'default': 'normal'}
|
855
857
|
])
|
856
858
|
def send_email(**kwargs):
|
857
859
|
"""发送邮件通知"""
|
858
860
|
to_email = kwargs.get('to_email')
|
859
|
-
subject = kwargs.get('subject')
|
860
|
-
content = kwargs.get('content')
|
861
|
+
subject = kwargs.get('subject', '测试邮件')
|
862
|
+
content = kwargs.get('content', '这是一封测试邮件')
|
863
|
+
priority = kwargs.get('priority', 'normal')
|
861
864
|
|
862
865
|
# 实现邮件发送逻辑
|
863
|
-
print(f"发送邮件到 {to_email}: {subject}")
|
866
|
+
print(f"发送邮件到 {to_email}: {subject} (优先级: {priority})")
|
864
867
|
|
865
868
|
return True
|
866
869
|
```
|
@@ -874,10 +877,71 @@ def send_email(**kwargs):
|
|
874
877
|
users = [数据库查询], 查询语句: "SELECT * FROM users WHERE active = 1"
|
875
878
|
[打印], 内容: "查询到 ${len(users)} 个活跃用户"
|
876
879
|
|
877
|
-
# 发送测试报告邮件
|
878
|
-
[发送邮件], 收件人: "admin@example.com"
|
880
|
+
# 发送测试报告邮件 - 使用默认值
|
881
|
+
[发送邮件], 收件人: "admin@example.com" # 主题和内容使用默认值
|
882
|
+
|
883
|
+
# 发送自定义邮件 - 覆盖默认值
|
884
|
+
[发送邮件], 收件人: "dev@example.com", 主题: "部署完成", 内容: "系统已成功部署到生产环境"
|
885
|
+
```
|
886
|
+
|
887
|
+
### 5. 参数默认值功能 🆕
|
888
|
+
|
889
|
+
pytest-dsl 现在支持为关键字参数设置默认值,让DSL编写更加简洁:
|
890
|
+
|
891
|
+
#### 定义带默认值的关键字
|
892
|
+
|
893
|
+
```python
|
894
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
895
|
+
|
896
|
+
@keyword_manager.register('HTTP请求', [
|
897
|
+
{'name': '地址', 'mapping': 'url', 'description': '请求地址'},
|
898
|
+
{'name': '方法', 'mapping': 'method', 'description': 'HTTP方法', 'default': 'GET'},
|
899
|
+
{'name': '超时', 'mapping': 'timeout', 'description': '超时时间(秒)', 'default': 30},
|
900
|
+
{'name': '重试次数', 'mapping': 'retries', 'description': '重试次数', 'default': 3},
|
901
|
+
{'name': '验证SSL', 'mapping': 'verify_ssl', 'description': '是否验证SSL证书', 'default': True}
|
902
|
+
])
|
903
|
+
def http_request(**kwargs):
|
904
|
+
"""HTTP请求关键字,支持默认值"""
|
905
|
+
url = kwargs.get('url')
|
906
|
+
method = kwargs.get('method', 'GET') # 默认值也会自动应用
|
907
|
+
timeout = kwargs.get('timeout', 30)
|
908
|
+
retries = kwargs.get('retries', 3)
|
909
|
+
verify_ssl = kwargs.get('verify_ssl', True)
|
910
|
+
|
911
|
+
# 执行HTTP请求逻辑
|
912
|
+
return {"status": "success", "method": method, "url": url}
|
879
913
|
```
|
880
914
|
|
915
|
+
#### 在DSL中使用默认值
|
916
|
+
|
917
|
+
```python
|
918
|
+
@name: "默认值功能演示"
|
919
|
+
|
920
|
+
# 只传递必需参数,其他使用默认值
|
921
|
+
response1 = [HTTP请求], 地址: "https://api.example.com/users"
|
922
|
+
# 等价于:方法: "GET", 超时: 30, 重试次数: 3, 验证SSL: True
|
923
|
+
|
924
|
+
# 部分覆盖默认值
|
925
|
+
response2 = [HTTP请求], 地址: "https://api.example.com/users", 方法: "POST", 超时: 60
|
926
|
+
# 只覆盖方法和超时,重试次数和SSL验证仍使用默认值
|
927
|
+
|
928
|
+
# 内置关键字也支持默认值
|
929
|
+
random_num = [生成随机数] # 使用默认范围 0-100,整数
|
930
|
+
custom_num = [生成随机数], 最大值: 50 # 只修改最大值,其他保持默认
|
931
|
+
|
932
|
+
# 生成随机字符串
|
933
|
+
default_string = [生成随机字符串] # 长度8,字母数字混合
|
934
|
+
custom_string = [生成随机字符串], 长度: 12, 类型: "letters" # 自定义长度和类型
|
935
|
+
```
|
936
|
+
|
937
|
+
#### 默认值的优势
|
938
|
+
|
939
|
+
- **🎯 简化调用** - 只需传递关键参数,常用配置自动应用
|
940
|
+
- **🔧 灵活覆盖** - 可选择性地覆盖任何默认值
|
941
|
+
- **📖 提高可读性** - DSL更加简洁,重点突出
|
942
|
+
- **🛡️ 减少错误** - 避免重复配置常用参数
|
943
|
+
- **🌐 远程支持** - 远程关键字也完整支持默认值功能
|
944
|
+
|
881
945
|
#### 支持远程模式的关键字
|
882
946
|
|
883
947
|
```python
|
@@ -1037,20 +1101,6 @@ CMD ["pytest-dsl", "tests/", "--yaml-vars", "config/prod.yaml"]
|
|
1037
1101
|
- **集成测试** - 跨系统测试协调
|
1038
1102
|
- **性能测试** - 结合其他工具进行性能测试
|
1039
1103
|
|
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
1104
|
## 📋 示例验证
|
1055
1105
|
|
1056
1106
|
本README.md中的大部分示例都已经过验证,确保可以正常运行。验证示例位于 `examples/readme_validation/` 目录中。
|
@@ -1086,3 +1136,18 @@ pytest-dsl api_basic.dsl
|
|
1086
1136
|
---
|
1087
1137
|
|
1088
1138
|
🚀 **开始使用pytest-dsl,让测试自动化变得简单而强大!**
|
1139
|
+
|
1140
|
+
|
1141
|
+
## 🤝 贡献与支持
|
1142
|
+
|
1143
|
+
我们欢迎您的贡献和反馈!
|
1144
|
+
|
1145
|
+
- 🐛 [报告问题](https://github.com/felix-1991/pytest-dsl/issues)
|
1146
|
+
- 💡 [功能建议](https://github.com/felix-1991/pytest-dsl/discussions)
|
1147
|
+
- 🔧 [提交PR](https://github.com/felix-1991/pytest-dsl/pulls)
|
1148
|
+
|
1149
|
+
## 📄 许可证
|
1150
|
+
|
1151
|
+
MIT License - 详见 [LICENSE](LICENSE) 文件
|
1152
|
+
|
1153
|
+
---
|
@@ -11,11 +11,12 @@ pytest-dsl是一个革命性的关键字驱动测试框架,基于pytest构建
|
|
11
11
|
## ✨ 核心特性
|
12
12
|
|
13
13
|
- 🎯 **门槛上手低** - 自然语言风格,只需少量编程基础
|
14
|
-
- 🔧 **高度可扩展** -
|
14
|
+
- 🔧 **高度可扩展** - 轻松创建自定义关键字,支持参数默认值
|
15
15
|
- 🌐 **分布式执行** - 支持远程关键字调用
|
16
16
|
- 🔄 **无缝集成** - 完美兼容pytest生态
|
17
17
|
- 📊 **丰富报告** - 集成Allure测试报告
|
18
18
|
- 🛡️ **企业级** - 支持变量管理、环境隔离
|
19
|
+
- ⚡ **智能简化** - 参数默认值让DSL更加简洁易读
|
19
20
|
|
20
21
|
## 🚀 5分钟快速开始
|
21
22
|
|
@@ -819,17 +820,19 @@ def database_query(**kwargs):
|
|
819
820
|
|
820
821
|
@keyword_manager.register('发送邮件', [
|
821
822
|
{'name': '收件人', 'mapping': 'to_email', 'description': '收件人邮箱'},
|
822
|
-
{'name': '主题', 'mapping': 'subject', 'description': '邮件主题'},
|
823
|
-
{'name': '内容', 'mapping': 'content', 'description': '邮件内容'}
|
823
|
+
{'name': '主题', 'mapping': 'subject', 'description': '邮件主题', 'default': '测试邮件'},
|
824
|
+
{'name': '内容', 'mapping': 'content', 'description': '邮件内容', 'default': '这是一封测试邮件'},
|
825
|
+
{'name': '优先级', 'mapping': 'priority', 'description': '邮件优先级', 'default': 'normal'}
|
824
826
|
])
|
825
827
|
def send_email(**kwargs):
|
826
828
|
"""发送邮件通知"""
|
827
829
|
to_email = kwargs.get('to_email')
|
828
|
-
subject = kwargs.get('subject')
|
829
|
-
content = kwargs.get('content')
|
830
|
+
subject = kwargs.get('subject', '测试邮件')
|
831
|
+
content = kwargs.get('content', '这是一封测试邮件')
|
832
|
+
priority = kwargs.get('priority', 'normal')
|
830
833
|
|
831
834
|
# 实现邮件发送逻辑
|
832
|
-
print(f"发送邮件到 {to_email}: {subject}")
|
835
|
+
print(f"发送邮件到 {to_email}: {subject} (优先级: {priority})")
|
833
836
|
|
834
837
|
return True
|
835
838
|
```
|
@@ -843,10 +846,71 @@ def send_email(**kwargs):
|
|
843
846
|
users = [数据库查询], 查询语句: "SELECT * FROM users WHERE active = 1"
|
844
847
|
[打印], 内容: "查询到 ${len(users)} 个活跃用户"
|
845
848
|
|
846
|
-
# 发送测试报告邮件
|
847
|
-
[发送邮件], 收件人: "admin@example.com"
|
849
|
+
# 发送测试报告邮件 - 使用默认值
|
850
|
+
[发送邮件], 收件人: "admin@example.com" # 主题和内容使用默认值
|
851
|
+
|
852
|
+
# 发送自定义邮件 - 覆盖默认值
|
853
|
+
[发送邮件], 收件人: "dev@example.com", 主题: "部署完成", 内容: "系统已成功部署到生产环境"
|
854
|
+
```
|
855
|
+
|
856
|
+
### 5. 参数默认值功能 🆕
|
857
|
+
|
858
|
+
pytest-dsl 现在支持为关键字参数设置默认值,让DSL编写更加简洁:
|
859
|
+
|
860
|
+
#### 定义带默认值的关键字
|
861
|
+
|
862
|
+
```python
|
863
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
864
|
+
|
865
|
+
@keyword_manager.register('HTTP请求', [
|
866
|
+
{'name': '地址', 'mapping': 'url', 'description': '请求地址'},
|
867
|
+
{'name': '方法', 'mapping': 'method', 'description': 'HTTP方法', 'default': 'GET'},
|
868
|
+
{'name': '超时', 'mapping': 'timeout', 'description': '超时时间(秒)', 'default': 30},
|
869
|
+
{'name': '重试次数', 'mapping': 'retries', 'description': '重试次数', 'default': 3},
|
870
|
+
{'name': '验证SSL', 'mapping': 'verify_ssl', 'description': '是否验证SSL证书', 'default': True}
|
871
|
+
])
|
872
|
+
def http_request(**kwargs):
|
873
|
+
"""HTTP请求关键字,支持默认值"""
|
874
|
+
url = kwargs.get('url')
|
875
|
+
method = kwargs.get('method', 'GET') # 默认值也会自动应用
|
876
|
+
timeout = kwargs.get('timeout', 30)
|
877
|
+
retries = kwargs.get('retries', 3)
|
878
|
+
verify_ssl = kwargs.get('verify_ssl', True)
|
879
|
+
|
880
|
+
# 执行HTTP请求逻辑
|
881
|
+
return {"status": "success", "method": method, "url": url}
|
848
882
|
```
|
849
883
|
|
884
|
+
#### 在DSL中使用默认值
|
885
|
+
|
886
|
+
```python
|
887
|
+
@name: "默认值功能演示"
|
888
|
+
|
889
|
+
# 只传递必需参数,其他使用默认值
|
890
|
+
response1 = [HTTP请求], 地址: "https://api.example.com/users"
|
891
|
+
# 等价于:方法: "GET", 超时: 30, 重试次数: 3, 验证SSL: True
|
892
|
+
|
893
|
+
# 部分覆盖默认值
|
894
|
+
response2 = [HTTP请求], 地址: "https://api.example.com/users", 方法: "POST", 超时: 60
|
895
|
+
# 只覆盖方法和超时,重试次数和SSL验证仍使用默认值
|
896
|
+
|
897
|
+
# 内置关键字也支持默认值
|
898
|
+
random_num = [生成随机数] # 使用默认范围 0-100,整数
|
899
|
+
custom_num = [生成随机数], 最大值: 50 # 只修改最大值,其他保持默认
|
900
|
+
|
901
|
+
# 生成随机字符串
|
902
|
+
default_string = [生成随机字符串] # 长度8,字母数字混合
|
903
|
+
custom_string = [生成随机字符串], 长度: 12, 类型: "letters" # 自定义长度和类型
|
904
|
+
```
|
905
|
+
|
906
|
+
#### 默认值的优势
|
907
|
+
|
908
|
+
- **🎯 简化调用** - 只需传递关键参数,常用配置自动应用
|
909
|
+
- **🔧 灵活覆盖** - 可选择性地覆盖任何默认值
|
910
|
+
- **📖 提高可读性** - DSL更加简洁,重点突出
|
911
|
+
- **🛡️ 减少错误** - 避免重复配置常用参数
|
912
|
+
- **🌐 远程支持** - 远程关键字也完整支持默认值功能
|
913
|
+
|
850
914
|
#### 支持远程模式的关键字
|
851
915
|
|
852
916
|
```python
|
@@ -1006,20 +1070,6 @@ CMD ["pytest-dsl", "tests/", "--yaml-vars", "config/prod.yaml"]
|
|
1006
1070
|
- **集成测试** - 跨系统测试协调
|
1007
1071
|
- **性能测试** - 结合其他工具进行性能测试
|
1008
1072
|
|
1009
|
-
## 🤝 贡献与支持
|
1010
|
-
|
1011
|
-
我们欢迎您的贡献和反馈!
|
1012
|
-
|
1013
|
-
- 🐛 [报告问题](https://github.com/your-repo/pytest-dsl/issues)
|
1014
|
-
- 💡 [功能建议](https://github.com/your-repo/pytest-dsl/discussions)
|
1015
|
-
- 🔧 [提交PR](https://github.com/your-repo/pytest-dsl/pulls)
|
1016
|
-
|
1017
|
-
## 📄 许可证
|
1018
|
-
|
1019
|
-
MIT License - 详见 [LICENSE](LICENSE) 文件
|
1020
|
-
|
1021
|
-
---
|
1022
|
-
|
1023
1073
|
## 📋 示例验证
|
1024
1074
|
|
1025
1075
|
本README.md中的大部分示例都已经过验证,确保可以正常运行。验证示例位于 `examples/readme_validation/` 目录中。
|
@@ -1055,3 +1105,18 @@ pytest-dsl api_basic.dsl
|
|
1055
1105
|
---
|
1056
1106
|
|
1057
1107
|
🚀 **开始使用pytest-dsl,让测试自动化变得简单而强大!**
|
1108
|
+
|
1109
|
+
|
1110
|
+
## 🤝 贡献与支持
|
1111
|
+
|
1112
|
+
我们欢迎您的贡献和反馈!
|
1113
|
+
|
1114
|
+
- 🐛 [报告问题](https://github.com/felix-1991/pytest-dsl/issues)
|
1115
|
+
- 💡 [功能建议](https://github.com/felix-1991/pytest-dsl/discussions)
|
1116
|
+
- 🔧 [提交PR](https://github.com/felix-1991/pytest-dsl/pulls)
|
1117
|
+
|
1118
|
+
## 📄 许可证
|
1119
|
+
|
1120
|
+
MIT License - 详见 [LICENSE](LICENSE) 文件
|
1121
|
+
|
1122
|
+
---
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "pytest-dsl"
|
7
|
-
version = "0.
|
7
|
+
version = "0.10.0"
|
8
8
|
description = "A DSL testing framework based on pytest"
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.9"
|
@@ -41,8 +41,13 @@ pytest_dsl = "pytest_dsl.plugin"
|
|
41
41
|
[project.scripts]
|
42
42
|
pytest-dsl = "pytest_dsl.cli:main"
|
43
43
|
pytest-dsl-server = "pytest_dsl.remote.keyword_server:main"
|
44
|
-
pytest-dsl-list = "pytest_dsl.cli:
|
44
|
+
pytest-dsl-list = "pytest_dsl.cli:main_list_keywords"
|
45
45
|
|
46
46
|
[project.urls]
|
47
47
|
"Homepage" = "https://github.com/felix-1991/pytest-dsl"
|
48
48
|
"Bug Tracker" = "https://github.com/felix-1991/pytest-dsl/issues"
|
49
|
+
|
50
|
+
|
51
|
+
[[tool.uv.index]]
|
52
|
+
name = "tuna"
|
53
|
+
url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
|
@@ -64,8 +64,12 @@ def parse_args():
|
|
64
64
|
)
|
65
65
|
list_parser.add_argument(
|
66
66
|
'--format', choices=['text', 'json'],
|
67
|
-
default='
|
68
|
-
help='输出格式:
|
67
|
+
default='json',
|
68
|
+
help='输出格式:json(默认) 或 text'
|
69
|
+
)
|
70
|
+
list_parser.add_argument(
|
71
|
+
'--output', '-o', type=str, default=None,
|
72
|
+
help='输出文件路径(仅对 json 格式有效,默认为 keywords.json)'
|
69
73
|
)
|
70
74
|
list_parser.add_argument(
|
71
75
|
'--filter', type=str, default=None,
|
@@ -88,7 +92,10 @@ def parse_args():
|
|
88
92
|
if '--list-keywords' in argv:
|
89
93
|
parser.add_argument('--list-keywords', action='store_true')
|
90
94
|
parser.add_argument(
|
91
|
-
'--format', choices=['text', 'json'], default='
|
95
|
+
'--format', choices=['text', 'json'], default='json'
|
96
|
+
)
|
97
|
+
parser.add_argument(
|
98
|
+
'--output', '-o', type=str, default=None
|
92
99
|
)
|
93
100
|
parser.add_argument('--filter', type=str, default=None)
|
94
101
|
parser.add_argument(
|
@@ -178,13 +185,22 @@ def format_keyword_info_text(keyword_name, keyword_info, show_category=True):
|
|
178
185
|
param_name = getattr(param, 'name', str(param))
|
179
186
|
param_mapping = getattr(param, 'mapping', '')
|
180
187
|
param_desc = getattr(param, 'description', '')
|
188
|
+
param_default = getattr(param, 'default', None)
|
181
189
|
|
190
|
+
# 构建参数描述
|
191
|
+
param_info = []
|
182
192
|
if param_mapping and param_mapping != param_name:
|
183
|
-
|
184
|
-
f" {param_name} ({param_mapping}): {param_desc}"
|
185
|
-
)
|
193
|
+
param_info.append(f"{param_name} ({param_mapping})")
|
186
194
|
else:
|
187
|
-
|
195
|
+
param_info.append(param_name)
|
196
|
+
|
197
|
+
param_info.append(f": {param_desc}")
|
198
|
+
|
199
|
+
# 添加默认值信息
|
200
|
+
if param_default is not None:
|
201
|
+
param_info.append(f" (默认值: {param_default})")
|
202
|
+
|
203
|
+
lines.append(f" {''.join(param_info)}")
|
188
204
|
else:
|
189
205
|
lines.append(" 参数: 无")
|
190
206
|
|
@@ -221,6 +237,12 @@ def format_keyword_info_json(keyword_name, keyword_info):
|
|
221
237
|
'mapping': getattr(param, 'mapping', ''),
|
222
238
|
'description': getattr(param, 'description', '')
|
223
239
|
}
|
240
|
+
|
241
|
+
# 添加默认值信息
|
242
|
+
param_default = getattr(param, 'default', None)
|
243
|
+
if param_default is not None:
|
244
|
+
param_data['default'] = param_default
|
245
|
+
|
224
246
|
keyword_data['parameters'].append(param_data)
|
225
247
|
|
226
248
|
# 函数文档
|
@@ -231,8 +253,8 @@ def format_keyword_info_json(keyword_name, keyword_info):
|
|
231
253
|
return keyword_data
|
232
254
|
|
233
255
|
|
234
|
-
def list_keywords(output_format='
|
235
|
-
category_filter='all'):
|
256
|
+
def list_keywords(output_format='json', name_filter=None,
|
257
|
+
category_filter='all', output_file=None):
|
236
258
|
"""罗列所有关键字信息"""
|
237
259
|
import json
|
238
260
|
|
@@ -319,7 +341,25 @@ def list_keywords(output_format='text', name_filter=None,
|
|
319
341
|
keyword_data = format_keyword_info_json(name, info)
|
320
342
|
keywords_data['keywords'].append(keyword_data)
|
321
343
|
|
322
|
-
|
344
|
+
json_output = json.dumps(keywords_data, ensure_ascii=False, indent=2)
|
345
|
+
|
346
|
+
# 确定输出文件名
|
347
|
+
if output_file is None:
|
348
|
+
output_file = 'keywords.json'
|
349
|
+
|
350
|
+
# 写入到文件
|
351
|
+
try:
|
352
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
353
|
+
f.write(json_output)
|
354
|
+
print(f"关键字信息已保存到文件: {output_file}")
|
355
|
+
print(f"共 {total_count} 个关键字")
|
356
|
+
for cat, count in category_counts.items():
|
357
|
+
cat_names = {'builtin': '内置', 'custom': '自定义', 'remote': '远程'}
|
358
|
+
print(f" {cat_names.get(cat, cat)}: {count} 个")
|
359
|
+
except Exception as e:
|
360
|
+
print(f"保存文件失败: {e}")
|
361
|
+
# 如果写入文件失败,则回退到打印
|
362
|
+
print(json_output)
|
323
363
|
|
324
364
|
|
325
365
|
def load_yaml_variables(args):
|
@@ -433,16 +473,19 @@ def main():
|
|
433
473
|
list_keywords(
|
434
474
|
output_format=args.format,
|
435
475
|
name_filter=args.filter,
|
436
|
-
category_filter=args.category
|
476
|
+
category_filter=args.category,
|
477
|
+
output_file=args.output
|
437
478
|
)
|
438
479
|
elif args.command == 'run':
|
439
480
|
run_dsl_tests(args)
|
440
481
|
elif args.command == 'list-keywords-compat':
|
441
482
|
# 向后兼容:旧的--list-keywords格式
|
483
|
+
output_file = getattr(args, 'output', None)
|
442
484
|
list_keywords(
|
443
485
|
output_format=args.format,
|
444
486
|
name_filter=args.filter,
|
445
|
-
category_filter=args.category
|
487
|
+
category_filter=args.category,
|
488
|
+
output_file=output_file
|
446
489
|
)
|
447
490
|
elif args.command == 'run-compat':
|
448
491
|
# 向后兼容:默认执行DSL测试
|
@@ -453,5 +496,38 @@ def main():
|
|
453
496
|
sys.exit(1)
|
454
497
|
|
455
498
|
|
499
|
+
def main_list_keywords():
|
500
|
+
"""关键字列表命令的专用入口点"""
|
501
|
+
parser = argparse.ArgumentParser(description='查看pytest-dsl可用关键字列表')
|
502
|
+
parser.add_argument(
|
503
|
+
'--format', choices=['text', 'json'],
|
504
|
+
default='json',
|
505
|
+
help='输出格式:json(默认) 或 text'
|
506
|
+
)
|
507
|
+
parser.add_argument(
|
508
|
+
'--output', '-o', type=str, default=None,
|
509
|
+
help='输出文件路径(仅对 json 格式有效,默认为 keywords.json)'
|
510
|
+
)
|
511
|
+
parser.add_argument(
|
512
|
+
'--filter', type=str, default=None,
|
513
|
+
help='过滤关键字名称(支持部分匹配)'
|
514
|
+
)
|
515
|
+
parser.add_argument(
|
516
|
+
'--category',
|
517
|
+
choices=['builtin', 'custom', 'remote', 'all'],
|
518
|
+
default='all',
|
519
|
+
help='关键字类别:builtin(内置)、custom(自定义)、remote(远程)、all(全部,默认)'
|
520
|
+
)
|
521
|
+
|
522
|
+
args = parser.parse_args()
|
523
|
+
|
524
|
+
list_keywords(
|
525
|
+
output_format=args.format,
|
526
|
+
name_filter=args.filter,
|
527
|
+
category_filter=args.category,
|
528
|
+
output_file=args.output
|
529
|
+
)
|
530
|
+
|
531
|
+
|
456
532
|
if __name__ == '__main__':
|
457
533
|
main()
|
@@ -1,13 +1,14 @@
|
|
1
|
-
from typing import Dict, Any, Callable, List
|
1
|
+
from typing import Dict, Any, Callable, List, Optional
|
2
2
|
import functools
|
3
3
|
import allure
|
4
4
|
|
5
5
|
|
6
6
|
class Parameter:
|
7
|
-
def __init__(self, name: str, mapping: str, description: str):
|
7
|
+
def __init__(self, name: str, mapping: str, description: str, default: Any = None):
|
8
8
|
self.name = name
|
9
9
|
self.mapping = mapping
|
10
10
|
self.description = description
|
11
|
+
self.default = default
|
11
12
|
|
12
13
|
|
13
14
|
class KeywordManager:
|
@@ -34,6 +35,7 @@ class KeywordManager:
|
|
34
35
|
|
35
36
|
param_list = [Parameter(**p) for p in parameters]
|
36
37
|
mapping = {p.name: p.mapping for p in param_list}
|
38
|
+
defaults = {p.mapping: p.default for p in param_list if p.default is not None}
|
37
39
|
|
38
40
|
# 自动添加 step_name 到 mapping 中
|
39
41
|
mapping["步骤名称"] = "step_name"
|
@@ -41,7 +43,8 @@ class KeywordManager:
|
|
41
43
|
self._keywords[name] = {
|
42
44
|
'func': wrapper,
|
43
45
|
'mapping': mapping,
|
44
|
-
'parameters': param_list
|
46
|
+
'parameters': param_list,
|
47
|
+
'defaults': defaults # 存储默认值
|
45
48
|
}
|
46
49
|
return wrapper
|
47
50
|
return decorator
|
@@ -51,7 +54,19 @@ class KeywordManager:
|
|
51
54
|
keyword_info = self._keywords.get(keyword_name)
|
52
55
|
if not keyword_info:
|
53
56
|
raise KeyError(f"未注册的关键字: {keyword_name}")
|
54
|
-
|
57
|
+
|
58
|
+
# 应用默认值
|
59
|
+
final_params = {}
|
60
|
+
defaults = keyword_info.get('defaults', {})
|
61
|
+
|
62
|
+
# 首先设置所有默认值
|
63
|
+
for param_key, default_value in defaults.items():
|
64
|
+
final_params[param_key] = default_value
|
65
|
+
|
66
|
+
# 然后用传入的参数覆盖默认值
|
67
|
+
final_params.update(params)
|
68
|
+
|
69
|
+
return keyword_info['func'](**final_params)
|
55
70
|
|
56
71
|
def get_keyword_info(self, keyword_name: str) -> Dict:
|
57
72
|
"""获取关键字信息"""
|
@@ -99,8 +114,9 @@ class KeywordManager:
|
|
99
114
|
description="自定义的步骤名称,用于在报告中显示"
|
100
115
|
))
|
101
116
|
for param in info['parameters']:
|
117
|
+
default_info = f" (默认值: {param.default})" if param.default is not None else ""
|
102
118
|
docs.append(
|
103
|
-
f" {param.name} ({param.mapping}): {param.description}")
|
119
|
+
f" {param.name} ({param.mapping}): {param.description}{default_info}")
|
104
120
|
docs.append("")
|
105
121
|
return "\n".join(docs)
|
106
122
|
|
@@ -48,7 +48,8 @@ def _compare_values(actual: Any, expected: Any, operator: str = "==") -> bool:
|
|
48
48
|
Args:
|
49
49
|
actual: 实际值
|
50
50
|
expected: 预期值
|
51
|
-
operator: 比较运算符 (==, !=, >, <, >=, <=, contains, not_contains,
|
51
|
+
operator: 比较运算符 (==, !=, >, <, >=, <=, contains, not_contains,
|
52
|
+
matches, and, or, not)
|
52
53
|
|
53
54
|
Returns:
|
54
55
|
比较结果 (True/False)
|
@@ -96,8 +97,9 @@ def _compare_values(actual: Any, expected: Any, operator: str = "==") -> bool:
|
|
96
97
|
|
97
98
|
|
98
99
|
@keyword_manager.register('断言', [
|
99
|
-
{'name': '条件', 'mapping': 'condition',
|
100
|
-
|
100
|
+
{'name': '条件', 'mapping': 'condition',
|
101
|
+
'description': '断言条件表达式,例如: "${value} == 100" 或 "1 + 1 == 2"'},
|
102
|
+
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息', 'default': '断言失败'},
|
101
103
|
])
|
102
104
|
def assert_condition(**kwargs):
|
103
105
|
"""执行表达式断言
|
@@ -116,9 +118,11 @@ def assert_condition(**kwargs):
|
|
116
118
|
message = kwargs.get('message', '断言失败')
|
117
119
|
context = kwargs.get('context')
|
118
120
|
|
119
|
-
# 简单解析表达式,支持 ==, !=, >, <, >=, <=, contains, not_contains,
|
121
|
+
# 简单解析表达式,支持 ==, !=, >, <, >=, <=, contains, not_contains,
|
122
|
+
# matches, in, and, or, not
|
120
123
|
# 格式: "left_value operator right_value" 或 "boolean_expression"
|
121
|
-
operators = ["==", "!=", ">", "<", ">=", "<=", "contains", "not_contains",
|
124
|
+
operators = ["==", "!=", ">", "<", ">=", "<=", "contains", "not_contains",
|
125
|
+
"matches", "in", "and", "or", "not"]
|
122
126
|
|
123
127
|
# 先检查是否包含这些操作符
|
124
128
|
operator_used = None
|
@@ -132,7 +136,8 @@ def assert_condition(**kwargs):
|
|
132
136
|
try:
|
133
137
|
# 对条件进行变量替换
|
134
138
|
if '${' in condition:
|
135
|
-
condition = context.executor.variable_replacer.replace_in_string(
|
139
|
+
condition = context.executor.variable_replacer.replace_in_string(
|
140
|
+
condition)
|
136
141
|
# 尝试直接求值
|
137
142
|
result = eval(condition)
|
138
143
|
if not isinstance(result, bool):
|
@@ -141,7 +146,8 @@ def assert_condition(**kwargs):
|
|
141
146
|
raise AssertionError(f"{message}. 布尔表达式求值为假: {condition}")
|
142
147
|
return True
|
143
148
|
except Exception as e:
|
144
|
-
raise AssertionError(
|
149
|
+
raise AssertionError(
|
150
|
+
f"{message}. 无法解析条件表达式: {condition}. 错误: {str(e)}")
|
145
151
|
|
146
152
|
# 解析左值和右值
|
147
153
|
left_value, right_value = condition.split(f" {operator_used} ", 1)
|
@@ -351,8 +357,8 @@ def assert_condition(**kwargs):
|
|
351
357
|
{'name': 'JSON数据', 'mapping': 'json_data', 'description': 'JSON数据(字符串或对象)'},
|
352
358
|
{'name': 'JSONPath', 'mapping': 'jsonpath', 'description': 'JSONPath表达式'},
|
353
359
|
{'name': '预期值', 'mapping': 'expected_value', 'description': '预期的值'},
|
354
|
-
{'name': '操作符', 'mapping': 'operator', 'description': '
|
355
|
-
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息'},
|
360
|
+
{'name': '操作符', 'mapping': 'operator', 'description': '比较操作符', 'default': '=='},
|
361
|
+
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息', 'default': 'JSON断言失败'},
|
356
362
|
])
|
357
363
|
def assert_json(**kwargs):
|
358
364
|
"""执行JSON断言
|
@@ -476,7 +482,7 @@ def extract_json(**kwargs):
|
|
476
482
|
@keyword_manager.register('类型断言', [
|
477
483
|
{'name': '值', 'mapping': 'value', 'description': '要检查的值'},
|
478
484
|
{'name': '类型', 'mapping': 'type', 'description': '预期的类型 (string, number, boolean, list, object, null)'},
|
479
|
-
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息'},
|
485
|
+
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息', 'default': '类型断言失败'},
|
480
486
|
])
|
481
487
|
def assert_type(**kwargs):
|
482
488
|
"""断言值的类型
|
@@ -545,8 +551,8 @@ def assert_type(**kwargs):
|
|
545
551
|
@keyword_manager.register('数据比较', [
|
546
552
|
{'name': '实际值', 'mapping': 'actual', 'description': '实际值'},
|
547
553
|
{'name': '预期值', 'mapping': 'expected', 'description': '预期值'},
|
548
|
-
{'name': '操作符', 'mapping': 'operator', 'description': '
|
549
|
-
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息'},
|
554
|
+
{'name': '操作符', 'mapping': 'operator', 'description': '比较操作符', 'default': '=='},
|
555
|
+
{'name': '消息', 'mapping': 'message', 'description': '断言失败时的错误消息', 'default': '数据比较失败'},
|
550
556
|
])
|
551
557
|
def compare_values(**kwargs):
|
552
558
|
"""比较两个值
|