pytest-dynamic-params 0.1.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_dynamic_params-0.1.0/LICENSE +21 -0
- pytest_dynamic_params-0.1.0/PKG-INFO +188 -0
- pytest_dynamic_params-0.1.0/README.md +145 -0
- pytest_dynamic_params-0.1.0/pyproject.toml +75 -0
- pytest_dynamic_params-0.1.0/setup.cfg +4 -0
- pytest_dynamic_params-0.1.0/setup.py +15 -0
- pytest_dynamic_params-0.1.0/src/dynamic_params/__init__.py +21 -0
- pytest_dynamic_params-0.1.0/src/dynamic_params/config.py +163 -0
- pytest_dynamic_params-0.1.0/src/dynamic_params/decorators.py +100 -0
- pytest_dynamic_params-0.1.0/src/dynamic_params/dependency.py +100 -0
- pytest_dynamic_params-0.1.0/src/dynamic_params/errors.py +84 -0
- pytest_dynamic_params-0.1.0/src/dynamic_params/lazy.py +84 -0
- pytest_dynamic_params-0.1.0/src/dynamic_params/plugin.py +293 -0
- pytest_dynamic_params-0.1.0/src/dynamic_params/py.typed +0 -0
- pytest_dynamic_params-0.1.0/src/dynamic_params/utils.py +23 -0
- pytest_dynamic_params-0.1.0/src/pytest_dynamic_params.egg-info/PKG-INFO +188 -0
- pytest_dynamic_params-0.1.0/src/pytest_dynamic_params.egg-info/SOURCES.txt +19 -0
- pytest_dynamic_params-0.1.0/src/pytest_dynamic_params.egg-info/dependency_links.txt +1 -0
- pytest_dynamic_params-0.1.0/src/pytest_dynamic_params.egg-info/entry_points.txt +2 -0
- pytest_dynamic_params-0.1.0/src/pytest_dynamic_params.egg-info/requires.txt +9 -0
- pytest_dynamic_params-0.1.0/src/pytest_dynamic_params.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ProgrammerChengPei
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-dynamic-params
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dynamic parameters plugin for pytest
|
|
5
|
+
Author-email: Your Name <your.email@example.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 ProgrammerChengPei
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Classifier: Framework :: Pytest
|
|
29
|
+
Classifier: Programming Language :: Python :: 3
|
|
30
|
+
Classifier: Operating System :: OS Independent
|
|
31
|
+
Requires-Python: >=3.8
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Requires-Dist: pytest
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: black; extra == "dev"
|
|
37
|
+
Requires-Dist: flake8; extra == "dev"
|
|
38
|
+
Requires-Dist: isort; extra == "dev"
|
|
39
|
+
Requires-Dist: mypy; extra == "dev"
|
|
40
|
+
Requires-Dist: bandit; extra == "dev"
|
|
41
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# pytest-dynamic-params
|
|
45
|
+
|
|
46
|
+
pytest-dynamic-params 是一个为 pytest 设计的动态参数化插件,通过声明式参数生成器自动处理依赖解析和参数化,支持懒加载、缓存机制和多作用域管理,与现有 pytest 机制无缝集成,简化复杂测试场景的参数管理,提高测试效率和可维护性。
|
|
47
|
+
|
|
48
|
+
## 特性
|
|
49
|
+
|
|
50
|
+
- **声明式参数生成**:使用装饰器定义参数生成器,专注于"生成什么"而非"如何生成"
|
|
51
|
+
- **自动依赖管理**:系统自动分析生成器函数的参数签名,确定依赖关系并按正确顺序执行
|
|
52
|
+
- **与现有机制兼容**:完全兼容现有的pytest机制,包括`@pytest.mark.parametrize`装饰器和fixture系统
|
|
53
|
+
- **性能优化**:支持懒加载和缓存机制,提高测试执行效率
|
|
54
|
+
- **灵活的作用域管理**:支持function、class、module、session四种作用域
|
|
55
|
+
- **智能错误检测**:自动检测并报告参数生成器之间的循环依赖
|
|
56
|
+
- **可配置性**:支持多种配置选项,满足不同场景需求
|
|
57
|
+
|
|
58
|
+
## 安装
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install -e .
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 快速开始
|
|
65
|
+
|
|
66
|
+
安装后,您可以立即在测试中使用动态参数装饰器:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from dynamic_params import param_generator, with_dynamic_params
|
|
70
|
+
|
|
71
|
+
@param_generator
|
|
72
|
+
def calculate_result(input_value):
|
|
73
|
+
return input_value * 2
|
|
74
|
+
|
|
75
|
+
@with_dynamic_params(result=calculate_result)
|
|
76
|
+
@pytest.mark.parametrize("input_value", [1, 2, 3])
|
|
77
|
+
def test_basic(input_value, result):
|
|
78
|
+
assert result == input_value * 2
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 核心概念
|
|
82
|
+
|
|
83
|
+
- **参数生成器**:使用`@param_generator`装饰器定义的函数,用于生成参数值
|
|
84
|
+
- **动态参数**:在运行时生成的参数,可以依赖其他参数或fixture
|
|
85
|
+
- **参数依赖**:参数之间的依赖关系,系统自动按依赖顺序生成参数
|
|
86
|
+
- **懒加载**:推迟参数生成到实际需要时执行,避免不必要的计算
|
|
87
|
+
- **缓存机制**:缓存参数生成结果,避免重复计算,提高性能
|
|
88
|
+
- **作用域管理**:控制参数生成器实例的生命周期,影响缓存和执行时机
|
|
89
|
+
|
|
90
|
+
## 使用示例
|
|
91
|
+
|
|
92
|
+
完整的使用示例请参见:
|
|
93
|
+
- `examples/basic_usage.py` - 基础用法示例(可直接复制使用)
|
|
94
|
+
- `examples/advanced_usage.py` - 高级用法示例(包含作用域、缓存和懒加载)
|
|
95
|
+
- `docs/usage-guide.md` - 详细使用指南
|
|
96
|
+
|
|
97
|
+
## 配置
|
|
98
|
+
|
|
99
|
+
可以在`pytest.ini`中配置插件行为:
|
|
100
|
+
|
|
101
|
+
```ini
|
|
102
|
+
[pytest]
|
|
103
|
+
# 动态参数化系统配置
|
|
104
|
+
dynamic_param_cache_enabled = true
|
|
105
|
+
dynamic_param_validation = strict
|
|
106
|
+
dynamic_param_log_level = INFO
|
|
107
|
+
|
|
108
|
+
# 缓存大小配置
|
|
109
|
+
dynamic_param_cache_size_function = 1000
|
|
110
|
+
dynamic_param_cache_size_class = 500
|
|
111
|
+
dynamic_param_cache_size_module = 200
|
|
112
|
+
dynamic_param_cache_size_session = 100
|
|
113
|
+
|
|
114
|
+
# 性能配置
|
|
115
|
+
dynamic_param_lazy_loading = true
|
|
116
|
+
dynamic_param_incremental_generation = true
|
|
117
|
+
|
|
118
|
+
# 测试标记
|
|
119
|
+
markers =
|
|
120
|
+
dynamic_param: 使用动态参数的测试
|
|
121
|
+
param_generator: 参数生成器函数
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
也可以通过环境变量配置:
|
|
125
|
+
- `PYTEST_DYNAMIC_PARAM_CACHE`: 控制缓存是否启用
|
|
126
|
+
- `PYTEST_DYNAMIC_PARAM_VALIDATION`: 验证级别
|
|
127
|
+
- `PYTEST_DYNAMIC_PARAM_LOG_LEVEL`: 日志级别
|
|
128
|
+
- `PYTEST_DYNAMIC_PARAM_LAZY_LOADING`: 懒加载设置
|
|
129
|
+
- `PYTEST_DYNAMIC_PARAM_INCREMENTAL`: 增量生成设置
|
|
130
|
+
- `PYTEST_DYNAMIC_PARAM_DEBUG`: 调试模式
|
|
131
|
+
- `PYTEST_DYNAMIC_PARAM_PROFILE`: 性能分析
|
|
132
|
+
- `PYTEST_DYNAMIC_PARAM_CACHE_DIR`: 缓存目录
|
|
133
|
+
|
|
134
|
+
## 项目结构
|
|
135
|
+
|
|
136
|
+
项目的主要组成部分:
|
|
137
|
+
|
|
138
|
+
- `src/` - 源代码
|
|
139
|
+
- `tests/` - 测试代码
|
|
140
|
+
- `examples/` - 使用示例
|
|
141
|
+
- `docs/` - 文档
|
|
142
|
+
- `specs/` - 项目规格说明
|
|
143
|
+
- `reports/` - 测试报告
|
|
144
|
+
|
|
145
|
+
有关详细的源码架构说明,请参见 [docs/structure.md](./docs/structure.md)。
|
|
146
|
+
|
|
147
|
+
## 运行测试
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# 运行单元测试
|
|
151
|
+
python -m pytest tests/unit/
|
|
152
|
+
|
|
153
|
+
# 运行功能测试
|
|
154
|
+
python -m pytest tests/functional/
|
|
155
|
+
|
|
156
|
+
# 运行集成测试
|
|
157
|
+
python -m pytest tests/integration/
|
|
158
|
+
|
|
159
|
+
# 运行性能测试
|
|
160
|
+
python -m pytest tests/performance/
|
|
161
|
+
|
|
162
|
+
# 运行所有测试
|
|
163
|
+
python -m pytest tests/
|
|
164
|
+
|
|
165
|
+
# 查看覆盖率
|
|
166
|
+
python -m pytest --cov=src.dynamic_params tests/unit/ --cov-report=html
|
|
167
|
+
|
|
168
|
+
# 生成完整报告(Allure + 覆盖率)
|
|
169
|
+
python -m pytest tests/ --alluredir=reports/allure-results -clean --cov=src.dynamic_params --cov-report=html:reports/coverage-html --cov-report=xml:reports/coverage.xml --cov-report=term
|
|
170
|
+
|
|
171
|
+
# 查看Allure报告
|
|
172
|
+
allure serve reports/allure-results
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## 测试报告
|
|
176
|
+
|
|
177
|
+
有关测试报告的详细说明,请参见:
|
|
178
|
+
- [docs/reports-readme.md](./docs/reports-readme.md) - 报告使用说明
|
|
179
|
+
- [docs/reports-summary.md](./docs/reports-summary.md) - 测试结果和覆盖率摘要
|
|
180
|
+
|
|
181
|
+
生成的报告位于 `reports/` 目录:
|
|
182
|
+
- Allure报告:`reports/allure-results`(可通过 `allure serve` 查看)
|
|
183
|
+
- 覆盖率HTML报告:`reports/coverage-html/index.html`
|
|
184
|
+
- 覆盖率XML报告:`reports/coverage.xml`
|
|
185
|
+
|
|
186
|
+
## 许可证
|
|
187
|
+
|
|
188
|
+
MIT
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# pytest-dynamic-params
|
|
2
|
+
|
|
3
|
+
pytest-dynamic-params 是一个为 pytest 设计的动态参数化插件,通过声明式参数生成器自动处理依赖解析和参数化,支持懒加载、缓存机制和多作用域管理,与现有 pytest 机制无缝集成,简化复杂测试场景的参数管理,提高测试效率和可维护性。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **声明式参数生成**:使用装饰器定义参数生成器,专注于"生成什么"而非"如何生成"
|
|
8
|
+
- **自动依赖管理**:系统自动分析生成器函数的参数签名,确定依赖关系并按正确顺序执行
|
|
9
|
+
- **与现有机制兼容**:完全兼容现有的pytest机制,包括`@pytest.mark.parametrize`装饰器和fixture系统
|
|
10
|
+
- **性能优化**:支持懒加载和缓存机制,提高测试执行效率
|
|
11
|
+
- **灵活的作用域管理**:支持function、class、module、session四种作用域
|
|
12
|
+
- **智能错误检测**:自动检测并报告参数生成器之间的循环依赖
|
|
13
|
+
- **可配置性**:支持多种配置选项,满足不同场景需求
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install -e .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 快速开始
|
|
22
|
+
|
|
23
|
+
安装后,您可以立即在测试中使用动态参数装饰器:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from dynamic_params import param_generator, with_dynamic_params
|
|
27
|
+
|
|
28
|
+
@param_generator
|
|
29
|
+
def calculate_result(input_value):
|
|
30
|
+
return input_value * 2
|
|
31
|
+
|
|
32
|
+
@with_dynamic_params(result=calculate_result)
|
|
33
|
+
@pytest.mark.parametrize("input_value", [1, 2, 3])
|
|
34
|
+
def test_basic(input_value, result):
|
|
35
|
+
assert result == input_value * 2
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 核心概念
|
|
39
|
+
|
|
40
|
+
- **参数生成器**:使用`@param_generator`装饰器定义的函数,用于生成参数值
|
|
41
|
+
- **动态参数**:在运行时生成的参数,可以依赖其他参数或fixture
|
|
42
|
+
- **参数依赖**:参数之间的依赖关系,系统自动按依赖顺序生成参数
|
|
43
|
+
- **懒加载**:推迟参数生成到实际需要时执行,避免不必要的计算
|
|
44
|
+
- **缓存机制**:缓存参数生成结果,避免重复计算,提高性能
|
|
45
|
+
- **作用域管理**:控制参数生成器实例的生命周期,影响缓存和执行时机
|
|
46
|
+
|
|
47
|
+
## 使用示例
|
|
48
|
+
|
|
49
|
+
完整的使用示例请参见:
|
|
50
|
+
- `examples/basic_usage.py` - 基础用法示例(可直接复制使用)
|
|
51
|
+
- `examples/advanced_usage.py` - 高级用法示例(包含作用域、缓存和懒加载)
|
|
52
|
+
- `docs/usage-guide.md` - 详细使用指南
|
|
53
|
+
|
|
54
|
+
## 配置
|
|
55
|
+
|
|
56
|
+
可以在`pytest.ini`中配置插件行为:
|
|
57
|
+
|
|
58
|
+
```ini
|
|
59
|
+
[pytest]
|
|
60
|
+
# 动态参数化系统配置
|
|
61
|
+
dynamic_param_cache_enabled = true
|
|
62
|
+
dynamic_param_validation = strict
|
|
63
|
+
dynamic_param_log_level = INFO
|
|
64
|
+
|
|
65
|
+
# 缓存大小配置
|
|
66
|
+
dynamic_param_cache_size_function = 1000
|
|
67
|
+
dynamic_param_cache_size_class = 500
|
|
68
|
+
dynamic_param_cache_size_module = 200
|
|
69
|
+
dynamic_param_cache_size_session = 100
|
|
70
|
+
|
|
71
|
+
# 性能配置
|
|
72
|
+
dynamic_param_lazy_loading = true
|
|
73
|
+
dynamic_param_incremental_generation = true
|
|
74
|
+
|
|
75
|
+
# 测试标记
|
|
76
|
+
markers =
|
|
77
|
+
dynamic_param: 使用动态参数的测试
|
|
78
|
+
param_generator: 参数生成器函数
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
也可以通过环境变量配置:
|
|
82
|
+
- `PYTEST_DYNAMIC_PARAM_CACHE`: 控制缓存是否启用
|
|
83
|
+
- `PYTEST_DYNAMIC_PARAM_VALIDATION`: 验证级别
|
|
84
|
+
- `PYTEST_DYNAMIC_PARAM_LOG_LEVEL`: 日志级别
|
|
85
|
+
- `PYTEST_DYNAMIC_PARAM_LAZY_LOADING`: 懒加载设置
|
|
86
|
+
- `PYTEST_DYNAMIC_PARAM_INCREMENTAL`: 增量生成设置
|
|
87
|
+
- `PYTEST_DYNAMIC_PARAM_DEBUG`: 调试模式
|
|
88
|
+
- `PYTEST_DYNAMIC_PARAM_PROFILE`: 性能分析
|
|
89
|
+
- `PYTEST_DYNAMIC_PARAM_CACHE_DIR`: 缓存目录
|
|
90
|
+
|
|
91
|
+
## 项目结构
|
|
92
|
+
|
|
93
|
+
项目的主要组成部分:
|
|
94
|
+
|
|
95
|
+
- `src/` - 源代码
|
|
96
|
+
- `tests/` - 测试代码
|
|
97
|
+
- `examples/` - 使用示例
|
|
98
|
+
- `docs/` - 文档
|
|
99
|
+
- `specs/` - 项目规格说明
|
|
100
|
+
- `reports/` - 测试报告
|
|
101
|
+
|
|
102
|
+
有关详细的源码架构说明,请参见 [docs/structure.md](./docs/structure.md)。
|
|
103
|
+
|
|
104
|
+
## 运行测试
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 运行单元测试
|
|
108
|
+
python -m pytest tests/unit/
|
|
109
|
+
|
|
110
|
+
# 运行功能测试
|
|
111
|
+
python -m pytest tests/functional/
|
|
112
|
+
|
|
113
|
+
# 运行集成测试
|
|
114
|
+
python -m pytest tests/integration/
|
|
115
|
+
|
|
116
|
+
# 运行性能测试
|
|
117
|
+
python -m pytest tests/performance/
|
|
118
|
+
|
|
119
|
+
# 运行所有测试
|
|
120
|
+
python -m pytest tests/
|
|
121
|
+
|
|
122
|
+
# 查看覆盖率
|
|
123
|
+
python -m pytest --cov=src.dynamic_params tests/unit/ --cov-report=html
|
|
124
|
+
|
|
125
|
+
# 生成完整报告(Allure + 覆盖率)
|
|
126
|
+
python -m pytest tests/ --alluredir=reports/allure-results -clean --cov=src.dynamic_params --cov-report=html:reports/coverage-html --cov-report=xml:reports/coverage.xml --cov-report=term
|
|
127
|
+
|
|
128
|
+
# 查看Allure报告
|
|
129
|
+
allure serve reports/allure-results
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 测试报告
|
|
133
|
+
|
|
134
|
+
有关测试报告的详细说明,请参见:
|
|
135
|
+
- [docs/reports-readme.md](./docs/reports-readme.md) - 报告使用说明
|
|
136
|
+
- [docs/reports-summary.md](./docs/reports-summary.md) - 测试结果和覆盖率摘要
|
|
137
|
+
|
|
138
|
+
生成的报告位于 `reports/` 目录:
|
|
139
|
+
- Allure报告:`reports/allure-results`(可通过 `allure serve` 查看)
|
|
140
|
+
- 覆盖率HTML报告:`reports/coverage-html/index.html`
|
|
141
|
+
- 覆盖率XML报告:`reports/coverage.xml`
|
|
142
|
+
|
|
143
|
+
## 许可证
|
|
144
|
+
|
|
145
|
+
MIT
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pytest-dynamic-params"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Dynamic parameters plugin for pytest"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Your Name", email = "your.email@example.com" }
|
|
8
|
+
]
|
|
9
|
+
license = { file = "LICENSE" }
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Framework :: Pytest",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Operating System :: OS Independent"
|
|
14
|
+
]
|
|
15
|
+
requires-python = ">=3.8"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"pytest"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
dev = [
|
|
22
|
+
"black",
|
|
23
|
+
"flake8",
|
|
24
|
+
"isort",
|
|
25
|
+
"mypy",
|
|
26
|
+
"bandit",
|
|
27
|
+
"pre-commit"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.entry-points.pytest11]
|
|
31
|
+
dynamic-params = "dynamic_params.plugin"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["setuptools", "wheel"]
|
|
35
|
+
build-backend = "setuptools.build_meta"
|
|
36
|
+
|
|
37
|
+
[tool.basedpyright]
|
|
38
|
+
pythonVersion = "3.8"
|
|
39
|
+
strict = true
|
|
40
|
+
reportMissingImports = true
|
|
41
|
+
reportMissingTypeStubs = true
|
|
42
|
+
reportUnusedVariable = true
|
|
43
|
+
reportUnusedImport = true
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
pythonpath = ["src"]
|
|
48
|
+
|
|
49
|
+
[tool.black]
|
|
50
|
+
line-length = 88
|
|
51
|
+
target-version = ['py38']
|
|
52
|
+
|
|
53
|
+
[tool.isort]
|
|
54
|
+
profile = "black"
|
|
55
|
+
|
|
56
|
+
[tool.flake8]
|
|
57
|
+
max-line-length = 88
|
|
58
|
+
extend-ignore = [
|
|
59
|
+
"E203", # 与 black 冲突的空格规则
|
|
60
|
+
"ANN101", "ANN102", "ANN103", "ANN104", # 忽略 self/cls 的类型注解检查
|
|
61
|
+
"ANN201", "ANN202", "ANN203", "ANN204", "ANN205", "ANN206", # 忽略返回值类型注解检查
|
|
62
|
+
"ANN001", "ANN002", "ANN003" # 忽略参数类型注解检查
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[tool.flake8.plugins]
|
|
66
|
+
flake8-annotations = false
|
|
67
|
+
|
|
68
|
+
[tool.mypy]
|
|
69
|
+
python_version = "3.10"
|
|
70
|
+
ignore_missing_imports = true
|
|
71
|
+
exclude = "^tests/|^venv/|^\\.venv/"
|
|
72
|
+
mypy_path = "src"
|
|
73
|
+
follow_imports = "silent"
|
|
74
|
+
warn_unused_configs = true
|
|
75
|
+
namespace_packages = true
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="pytest-dynamic-params",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
packages=find_packages(where="src"), # 只查找src目录下的包
|
|
7
|
+
package_dir={"": "src"},
|
|
8
|
+
install_requires=["pytest"],
|
|
9
|
+
entry_points={"pytest11": ["dynamic-params = dynamic_params.plugin"]},
|
|
10
|
+
classifiers=[
|
|
11
|
+
"Framework :: Pytest",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
],
|
|
15
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .config import DynamicParamConfig
|
|
2
|
+
from .core.generator import ParamGenerator
|
|
3
|
+
from .core.registry import GeneratorRegistry
|
|
4
|
+
from .decorators import param_generator, with_dynamic_params
|
|
5
|
+
from .errors import DynamicParamError, InvalidGeneratorError, MissingParameterError
|
|
6
|
+
from .lazy import LazyResult
|
|
7
|
+
from .plugin import pytest_configure, pytest_generate_tests
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"ParamGenerator",
|
|
11
|
+
"LazyResult",
|
|
12
|
+
"GeneratorRegistry",
|
|
13
|
+
"DynamicParamError",
|
|
14
|
+
"MissingParameterError",
|
|
15
|
+
"InvalidGeneratorError",
|
|
16
|
+
"param_generator",
|
|
17
|
+
"with_dynamic_params",
|
|
18
|
+
"pytest_configure",
|
|
19
|
+
"pytest_generate_tests",
|
|
20
|
+
"DynamicParamConfig",
|
|
21
|
+
]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from .errors import ConfigurationError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DynamicParamConfig:
|
|
9
|
+
"""动态参数化系统配置类"""
|
|
10
|
+
|
|
11
|
+
DEFAULT_CONFIG = {
|
|
12
|
+
"cache": {
|
|
13
|
+
"enabled": "true",
|
|
14
|
+
"size_function": "1000",
|
|
15
|
+
"size_class": "500",
|
|
16
|
+
"size_module": "200",
|
|
17
|
+
"size_session": "100",
|
|
18
|
+
"cleanup_interval": "100",
|
|
19
|
+
"dir": ".pytest_cache/dynamic_params",
|
|
20
|
+
},
|
|
21
|
+
"validation": {"level": "strict", "log_level": "INFO"},
|
|
22
|
+
"performance": {"lazy_loading": "true", "incremental_generation": "true"},
|
|
23
|
+
"debug": {"enabled": "false", "profile": "false"},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_instance: Optional["DynamicParamConfig"] = None
|
|
27
|
+
_config: Dict[str, Any]
|
|
28
|
+
|
|
29
|
+
def __new__(cls):
|
|
30
|
+
if cls._instance is None:
|
|
31
|
+
cls._instance = super().__new__(cls)
|
|
32
|
+
cls._instance._config = cls._instance._load_config()
|
|
33
|
+
return cls._instance
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def get_instance(cls):
|
|
37
|
+
"""获取单例实例"""
|
|
38
|
+
if cls._instance is None:
|
|
39
|
+
cls._instance = cls()
|
|
40
|
+
return cls._instance
|
|
41
|
+
|
|
42
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
43
|
+
"""加载配置"""
|
|
44
|
+
config = configparser.ConfigParser()
|
|
45
|
+
config.read_dict(self.DEFAULT_CONFIG)
|
|
46
|
+
|
|
47
|
+
# 从配置文件加载(优先级:pytest.ini > pyproject.toml > dynamic_params.json/yaml)
|
|
48
|
+
config_files = [
|
|
49
|
+
"pytest.ini",
|
|
50
|
+
"pyproject.toml",
|
|
51
|
+
"dynamic_params.json",
|
|
52
|
+
"dynamic_params.yaml",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
for config_file in config_files:
|
|
56
|
+
if os.path.exists(config_file):
|
|
57
|
+
try:
|
|
58
|
+
config.read(config_file)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"Warning: Failed to read {config_file}: {e}")
|
|
61
|
+
|
|
62
|
+
# 从环境变量加载(优先级最高)
|
|
63
|
+
self._update_from_env(config)
|
|
64
|
+
|
|
65
|
+
return self._normalize_config(config)
|
|
66
|
+
|
|
67
|
+
def _update_from_env(self, config: configparser.ConfigParser):
|
|
68
|
+
"""从环境变量更新配置"""
|
|
69
|
+
env_mapping = {
|
|
70
|
+
"PYTEST_DYNAMIC_PARAM_CACHE": "cache.enabled",
|
|
71
|
+
"PYTEST_DYNAMIC_PARAM_VALIDATION": "validation.level",
|
|
72
|
+
"PYTEST_DYNAMIC_PARAM_LOG_LEVEL": "validation.log_level",
|
|
73
|
+
"PYTEST_DYNAMIC_PARAM_LAZY_LOADING": "performance.lazy_loading",
|
|
74
|
+
"PYTEST_DYNAMIC_PARAM_INCREMENTAL": "performance.incremental_generation",
|
|
75
|
+
"PYTEST_DYNAMIC_PARAM_DEBUG": "debug.enabled",
|
|
76
|
+
"PYTEST_DYNAMIC_PARAM_PROFILE": "debug.profile",
|
|
77
|
+
"PYTEST_DYNAMIC_PARAM_CACHE_DIR": "cache.dir",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for env_var, config_key in env_mapping.items():
|
|
81
|
+
if env_var in os.environ:
|
|
82
|
+
try:
|
|
83
|
+
section, option = config_key.split(".")
|
|
84
|
+
if section not in config:
|
|
85
|
+
config[section] = {}
|
|
86
|
+
config[section][option] = os.environ[env_var]
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f"Warning: Failed to update config from {env_var}: {e}")
|
|
89
|
+
|
|
90
|
+
def _normalize_config(self, config: configparser.ConfigParser) -> Dict[str, Any]:
|
|
91
|
+
"""标准化配置值"""
|
|
92
|
+
normalized: Dict[str, Any] = {}
|
|
93
|
+
|
|
94
|
+
for section in config.sections():
|
|
95
|
+
normalized[section] = {}
|
|
96
|
+
for key, value in config[section].items():
|
|
97
|
+
try:
|
|
98
|
+
# 转换布尔值
|
|
99
|
+
if value.lower() in ("true", "false"):
|
|
100
|
+
normalized[section][key] = config[section].getboolean(key)
|
|
101
|
+
# 转换整数
|
|
102
|
+
elif value.isdigit():
|
|
103
|
+
normalized[section][key] = config[section].getint(key)
|
|
104
|
+
else:
|
|
105
|
+
normalized[section][key] = value
|
|
106
|
+
except Exception as e:
|
|
107
|
+
raise ConfigurationError(
|
|
108
|
+
config_key=f"{section}.{key}",
|
|
109
|
+
config_value=value,
|
|
110
|
+
expected_type=str,
|
|
111
|
+
) from e
|
|
112
|
+
|
|
113
|
+
return normalized
|
|
114
|
+
|
|
115
|
+
def get(self, section: str, option: str, default: Optional[Any] = ...) -> Any:
|
|
116
|
+
"""获取配置值"""
|
|
117
|
+
try:
|
|
118
|
+
if section not in self._config:
|
|
119
|
+
if default is not ...:
|
|
120
|
+
return default
|
|
121
|
+
raise KeyError(section)
|
|
122
|
+
if option not in self._config[section]:
|
|
123
|
+
if default is not ...:
|
|
124
|
+
return default
|
|
125
|
+
raise KeyError(option)
|
|
126
|
+
return self._config[section][option]
|
|
127
|
+
except KeyError:
|
|
128
|
+
if default is not ...:
|
|
129
|
+
return default
|
|
130
|
+
raise
|
|
131
|
+
|
|
132
|
+
def get_section(self, section: str) -> Dict[str, Any]:
|
|
133
|
+
"""获取整个配置节"""
|
|
134
|
+
return self._config.get(section, {})
|
|
135
|
+
|
|
136
|
+
def validate(self) -> bool:
|
|
137
|
+
"""验证配置有效性"""
|
|
138
|
+
try:
|
|
139
|
+
# 验证缓存配置
|
|
140
|
+
if not isinstance(self.get("cache", "enabled"), bool):
|
|
141
|
+
raise ConfigurationError(
|
|
142
|
+
"cache.enabled", self.get("cache", "enabled"), bool
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# 验证验证级别
|
|
146
|
+
valid_levels = ["strict", "warn", "off"]
|
|
147
|
+
if self.get("validation", "level") not in valid_levels:
|
|
148
|
+
raise ConfigurationError(
|
|
149
|
+
"validation.level", self.get("validation", "level"), str
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# 验证日志级别
|
|
153
|
+
valid_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
|
|
154
|
+
if self.get("validation", "log_level") not in valid_log_levels:
|
|
155
|
+
raise ConfigurationError(
|
|
156
|
+
"validation.log_level", self.get("validation", "log_level"), str
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return True
|
|
160
|
+
except ConfigurationError:
|
|
161
|
+
raise
|
|
162
|
+
except Exception as e:
|
|
163
|
+
raise ConfigurationError("config.validation", str(e), str) from e
|