pytest-datadriver 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_datadriver-0.1.0/PKG-INFO +199 -0
- pytest_datadriver-0.1.0/README.md +175 -0
- pytest_datadriver-0.1.0/pyproject.toml +42 -0
- pytest_datadriver-0.1.0/pytest_datadriver/__init__.py +28 -0
- pytest_datadriver-0.1.0/pytest_datadriver/constants.py +61 -0
- pytest_datadriver-0.1.0/pytest_datadriver/exceptions.py +52 -0
- pytest_datadriver-0.1.0/pytest_datadriver/exporters/__init__.py +60 -0
- pytest_datadriver-0.1.0/pytest_datadriver/exporters/base.py +63 -0
- pytest_datadriver-0.1.0/pytest_datadriver/exporters/json_exporter.py +24 -0
- pytest_datadriver-0.1.0/pytest_datadriver/exporters/yaml_exporter.py +32 -0
- pytest_datadriver-0.1.0/pytest_datadriver/extractor.py +389 -0
- pytest_datadriver-0.1.0/pytest_datadriver/loaders/__init__.py +49 -0
- pytest_datadriver-0.1.0/pytest_datadriver/loaders/base.py +35 -0
- pytest_datadriver-0.1.0/pytest_datadriver/loaders/json_loader.py +40 -0
- pytest_datadriver-0.1.0/pytest_datadriver/loaders/yaml_loader.py +135 -0
- pytest_datadriver-0.1.0/pytest_datadriver/models.py +76 -0
- pytest_datadriver-0.1.0/pytest_datadriver/plugin.py +254 -0
- pytest_datadriver-0.1.0/pytest_datadriver/resolver.py +205 -0
- pytest_datadriver-0.1.0/pytest_datadriver.egg-info/PKG-INFO +199 -0
- pytest_datadriver-0.1.0/pytest_datadriver.egg-info/SOURCES.txt +27 -0
- pytest_datadriver-0.1.0/pytest_datadriver.egg-info/dependency_links.txt +1 -0
- pytest_datadriver-0.1.0/pytest_datadriver.egg-info/entry_points.txt +2 -0
- pytest_datadriver-0.1.0/pytest_datadriver.egg-info/requires.txt +6 -0
- pytest_datadriver-0.1.0/pytest_datadriver.egg-info/top_level.txt +1 -0
- pytest_datadriver-0.1.0/setup.cfg +4 -0
- pytest_datadriver-0.1.0/tests/test_extractor.py +199 -0
- pytest_datadriver-0.1.0/tests/test_loaders.py +188 -0
- pytest_datadriver-0.1.0/tests/test_plugin.py +113 -0
- pytest_datadriver-0.1.0/tests/test_resolver.py +171 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-datadriver
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A pytest plugin for data-driven testing with YAML/JSON external data files
|
|
5
|
+
Author: CIB Test Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: pytest,data-driven,yaml,json,testing
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Framework :: Pytest
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Testing
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: pytest>=7.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
24
|
+
|
|
25
|
+
# pytest-datadriver
|
|
26
|
+
|
|
27
|
+
一个 pytest 插件,支持通过 YAML/JSON 外部数据文件实现数据驱动测试。
|
|
28
|
+
|
|
29
|
+
## 特性
|
|
30
|
+
|
|
31
|
+
- **多格式支持**:YAML 和 JSON 外部数据文件,可扩展架构
|
|
32
|
+
- **零侵入**:无需修改现有测试代码,通过 CLI 参数可选激活
|
|
33
|
+
- **参数自动提取**:基于 AST 静态分析,从现有测试代码提取 parametrize 参数并导出
|
|
34
|
+
- **双模式覆盖**:智能覆盖(按 case_no 匹配)和完全替换模式
|
|
35
|
+
- **两种数据布局**:case-first 和 function-first
|
|
36
|
+
- **fixture 支持**:自动识别 fixture 参数与 parametrize 参数
|
|
37
|
+
|
|
38
|
+
## 安装
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install pytest-datadriver
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
或从源码安装:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone <repo-url>
|
|
48
|
+
cd cib-pytest-datadriver
|
|
49
|
+
pip install -e .
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 快速开始
|
|
53
|
+
|
|
54
|
+
### 1. 在测试代码中添加标记
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import pytest
|
|
58
|
+
|
|
59
|
+
@pytest.mark.caseNo("TC-001")
|
|
60
|
+
@pytest.mark.caseName("加法测试")
|
|
61
|
+
@pytest.mark.parametrize("a,b,expected", [
|
|
62
|
+
(1, 2, 3),
|
|
63
|
+
(5, 10, 15),
|
|
64
|
+
])
|
|
65
|
+
def test_addition(a, b, expected):
|
|
66
|
+
assert a + b == expected
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. 提取参数到数据文件
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pytest --data-extract --data-output TestData/data.yaml --test-path tests/
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. 使用数据文件运行测试
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 智能覆盖(默认):按 case_no 匹配覆盖参数
|
|
79
|
+
pytest tests/ --data-file TestData/data.yaml --data-mode smart
|
|
80
|
+
|
|
81
|
+
# 完全替换:只执行文件中的用例
|
|
82
|
+
pytest tests/ --data-file TestData/data.yaml --data-mode replace
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## CLI 参数
|
|
86
|
+
|
|
87
|
+
| 参数 | 说明 | 可选值 | 默认值 |
|
|
88
|
+
|------|------|--------|--------|
|
|
89
|
+
| `--data-file` | 数据文件路径 | - | 自动查找 TestData/data.yaml |
|
|
90
|
+
| `--data-mode` | 覆盖模式 | smart, replace | smart |
|
|
91
|
+
| `--data-layout` | 数据布局 | case, func | case |
|
|
92
|
+
| `--data-format` | 数据格式 | yaml, json, auto | auto |
|
|
93
|
+
| `--data-extract` | 触发提取模式 | - | - |
|
|
94
|
+
| `--data-output` | 提取输出路径 | - | TestData/data.yaml |
|
|
95
|
+
| `--data-export-format` | 导出详情级别 | simple, full | simple |
|
|
96
|
+
| `--data-export-filter` | 导出过滤 | all, case-only | all |
|
|
97
|
+
| `--data-module-match` | 模块匹配策略 | fullpath, filename | fullpath |
|
|
98
|
+
| `--test-path` | 提取扫描目录 | - | tests/ |
|
|
99
|
+
|
|
100
|
+
## 数据文件格式
|
|
101
|
+
|
|
102
|
+
### case-first 布局(默认)
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
TC-001:
|
|
106
|
+
case_name: "加法测试"
|
|
107
|
+
module: "tests/test_math.py"
|
|
108
|
+
function: "test_addition"
|
|
109
|
+
params:
|
|
110
|
+
a: 1
|
|
111
|
+
b: 2
|
|
112
|
+
expected: 3
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### function-first 布局
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
"tests/test_math.py::test_addition":
|
|
119
|
+
- case_no: "TC-001"
|
|
120
|
+
case_name: "加法测试"
|
|
121
|
+
params:
|
|
122
|
+
a: 1
|
|
123
|
+
b: 2
|
|
124
|
+
expected: 3
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 依赖
|
|
128
|
+
|
|
129
|
+
- Python >= 3.9
|
|
130
|
+
- pytest >= 7.0
|
|
131
|
+
- pyyaml >= 6.0
|
|
132
|
+
|
|
133
|
+
## 打包与发布
|
|
134
|
+
|
|
135
|
+
### 本地打包
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# 安装构建工具
|
|
139
|
+
pip install build twine
|
|
140
|
+
|
|
141
|
+
# 构建分发包(sdist + wheel)
|
|
142
|
+
python -m build
|
|
143
|
+
|
|
144
|
+
# 构建产物位于 dist/ 目录:
|
|
145
|
+
# dist/pytest_datadriver-0.1.0.tar.gz
|
|
146
|
+
# dist/pytest_datadriver-0.1.0-py3-none-any.whl
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 上传到 PyPI
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# 上传到 PyPI(需先配置 ~/.pypirc 或使用 Token)
|
|
153
|
+
twine upload dist/*
|
|
154
|
+
|
|
155
|
+
# 上传到 Test PyPI(预发布验证)
|
|
156
|
+
twine upload --repository testpypi dist/*
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 上传到私有制品库
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# 上传到私有 PyPI 仓库(需在 ~/.pypirc 中配置仓库地址)
|
|
163
|
+
twine upload --repository <your-repo-name> dist/*
|
|
164
|
+
|
|
165
|
+
# 示例:上传到腾讯云软件源
|
|
166
|
+
twine upload --repository-url https://mirrors.tencent.com/repository/pypi/tencent_pypi/simple dist/*
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 从制品库安装
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# 从 PyPI 安装
|
|
173
|
+
pip install pytest-datadriver
|
|
174
|
+
|
|
175
|
+
# 从私有制品库安装
|
|
176
|
+
pip install pytest-datadriver -i https://mirrors.tencent.com/repository/pypi/tencent_pypi/simple --trusted-host mirrors.tencent.com
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### ~/.pypirc 配置示例
|
|
180
|
+
|
|
181
|
+
```ini
|
|
182
|
+
[distutils]
|
|
183
|
+
index-servers =
|
|
184
|
+
pypi
|
|
185
|
+
tencent
|
|
186
|
+
|
|
187
|
+
[pypi]
|
|
188
|
+
username = __token__
|
|
189
|
+
password = pypi-xxxxxxxxxxxx
|
|
190
|
+
|
|
191
|
+
[tencent]
|
|
192
|
+
repository = https://mirrors.tencent.com/repository/pypi/tencent_pypi/simple
|
|
193
|
+
username = <your-username>
|
|
194
|
+
password = <your-password>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 许可证
|
|
198
|
+
|
|
199
|
+
MIT
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# pytest-datadriver
|
|
2
|
+
|
|
3
|
+
一个 pytest 插件,支持通过 YAML/JSON 外部数据文件实现数据驱动测试。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **多格式支持**:YAML 和 JSON 外部数据文件,可扩展架构
|
|
8
|
+
- **零侵入**:无需修改现有测试代码,通过 CLI 参数可选激活
|
|
9
|
+
- **参数自动提取**:基于 AST 静态分析,从现有测试代码提取 parametrize 参数并导出
|
|
10
|
+
- **双模式覆盖**:智能覆盖(按 case_no 匹配)和完全替换模式
|
|
11
|
+
- **两种数据布局**:case-first 和 function-first
|
|
12
|
+
- **fixture 支持**:自动识别 fixture 参数与 parametrize 参数
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install pytest-datadriver
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
或从源码安装:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git clone <repo-url>
|
|
24
|
+
cd cib-pytest-datadriver
|
|
25
|
+
pip install -e .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
### 1. 在测试代码中添加标记
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import pytest
|
|
34
|
+
|
|
35
|
+
@pytest.mark.caseNo("TC-001")
|
|
36
|
+
@pytest.mark.caseName("加法测试")
|
|
37
|
+
@pytest.mark.parametrize("a,b,expected", [
|
|
38
|
+
(1, 2, 3),
|
|
39
|
+
(5, 10, 15),
|
|
40
|
+
])
|
|
41
|
+
def test_addition(a, b, expected):
|
|
42
|
+
assert a + b == expected
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. 提取参数到数据文件
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pytest --data-extract --data-output TestData/data.yaml --test-path tests/
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. 使用数据文件运行测试
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 智能覆盖(默认):按 case_no 匹配覆盖参数
|
|
55
|
+
pytest tests/ --data-file TestData/data.yaml --data-mode smart
|
|
56
|
+
|
|
57
|
+
# 完全替换:只执行文件中的用例
|
|
58
|
+
pytest tests/ --data-file TestData/data.yaml --data-mode replace
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## CLI 参数
|
|
62
|
+
|
|
63
|
+
| 参数 | 说明 | 可选值 | 默认值 |
|
|
64
|
+
|------|------|--------|--------|
|
|
65
|
+
| `--data-file` | 数据文件路径 | - | 自动查找 TestData/data.yaml |
|
|
66
|
+
| `--data-mode` | 覆盖模式 | smart, replace | smart |
|
|
67
|
+
| `--data-layout` | 数据布局 | case, func | case |
|
|
68
|
+
| `--data-format` | 数据格式 | yaml, json, auto | auto |
|
|
69
|
+
| `--data-extract` | 触发提取模式 | - | - |
|
|
70
|
+
| `--data-output` | 提取输出路径 | - | TestData/data.yaml |
|
|
71
|
+
| `--data-export-format` | 导出详情级别 | simple, full | simple |
|
|
72
|
+
| `--data-export-filter` | 导出过滤 | all, case-only | all |
|
|
73
|
+
| `--data-module-match` | 模块匹配策略 | fullpath, filename | fullpath |
|
|
74
|
+
| `--test-path` | 提取扫描目录 | - | tests/ |
|
|
75
|
+
|
|
76
|
+
## 数据文件格式
|
|
77
|
+
|
|
78
|
+
### case-first 布局(默认)
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
TC-001:
|
|
82
|
+
case_name: "加法测试"
|
|
83
|
+
module: "tests/test_math.py"
|
|
84
|
+
function: "test_addition"
|
|
85
|
+
params:
|
|
86
|
+
a: 1
|
|
87
|
+
b: 2
|
|
88
|
+
expected: 3
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### function-first 布局
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
"tests/test_math.py::test_addition":
|
|
95
|
+
- case_no: "TC-001"
|
|
96
|
+
case_name: "加法测试"
|
|
97
|
+
params:
|
|
98
|
+
a: 1
|
|
99
|
+
b: 2
|
|
100
|
+
expected: 3
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 依赖
|
|
104
|
+
|
|
105
|
+
- Python >= 3.9
|
|
106
|
+
- pytest >= 7.0
|
|
107
|
+
- pyyaml >= 6.0
|
|
108
|
+
|
|
109
|
+
## 打包与发布
|
|
110
|
+
|
|
111
|
+
### 本地打包
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# 安装构建工具
|
|
115
|
+
pip install build twine
|
|
116
|
+
|
|
117
|
+
# 构建分发包(sdist + wheel)
|
|
118
|
+
python -m build
|
|
119
|
+
|
|
120
|
+
# 构建产物位于 dist/ 目录:
|
|
121
|
+
# dist/pytest_datadriver-0.1.0.tar.gz
|
|
122
|
+
# dist/pytest_datadriver-0.1.0-py3-none-any.whl
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 上传到 PyPI
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# 上传到 PyPI(需先配置 ~/.pypirc 或使用 Token)
|
|
129
|
+
twine upload dist/*
|
|
130
|
+
|
|
131
|
+
# 上传到 Test PyPI(预发布验证)
|
|
132
|
+
twine upload --repository testpypi dist/*
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 上传到私有制品库
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# 上传到私有 PyPI 仓库(需在 ~/.pypirc 中配置仓库地址)
|
|
139
|
+
twine upload --repository <your-repo-name> dist/*
|
|
140
|
+
|
|
141
|
+
# 示例:上传到腾讯云软件源
|
|
142
|
+
twine upload --repository-url https://mirrors.tencent.com/repository/pypi/tencent_pypi/simple dist/*
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 从制品库安装
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# 从 PyPI 安装
|
|
149
|
+
pip install pytest-datadriver
|
|
150
|
+
|
|
151
|
+
# 从私有制品库安装
|
|
152
|
+
pip install pytest-datadriver -i https://mirrors.tencent.com/repository/pypi/tencent_pypi/simple --trusted-host mirrors.tencent.com
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### ~/.pypirc 配置示例
|
|
156
|
+
|
|
157
|
+
```ini
|
|
158
|
+
[distutils]
|
|
159
|
+
index-servers =
|
|
160
|
+
pypi
|
|
161
|
+
tencent
|
|
162
|
+
|
|
163
|
+
[pypi]
|
|
164
|
+
username = __token__
|
|
165
|
+
password = pypi-xxxxxxxxxxxx
|
|
166
|
+
|
|
167
|
+
[tencent]
|
|
168
|
+
repository = https://mirrors.tencent.com/repository/pypi/tencent_pypi/simple
|
|
169
|
+
username = <your-username>
|
|
170
|
+
password = <your-password>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 许可证
|
|
174
|
+
|
|
175
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pytest-datadriver"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A pytest plugin for data-driven testing with YAML/JSON external data files"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "CIB Test Team" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["pytest", "data-driven", "yaml", "json", "testing"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Framework :: Pytest",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Topic :: Software Development :: Testing",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"pytest>=7.0",
|
|
29
|
+
"pyyaml>=6.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7.0",
|
|
35
|
+
"pytest-cov>=4.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.entry-points.pytest11]
|
|
39
|
+
datadriver = "pytest_datadriver.plugin"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
include = ["pytest_datadriver*"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""pytest-datadriver: A pytest plugin for data-driven testing with YAML/JSON external data files."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from pytest_datadriver.exceptions import (
|
|
6
|
+
DataDriverError,
|
|
7
|
+
DataFileNotFoundError,
|
|
8
|
+
DataFileFormatError,
|
|
9
|
+
DuplicateModuleError,
|
|
10
|
+
CaseNotFoundError,
|
|
11
|
+
ExtractionError,
|
|
12
|
+
)
|
|
13
|
+
from pytest_datadriver.models import CaseEntry, ParamSet, ExportContext, LayoutMode, OverrideMode
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"__version__",
|
|
17
|
+
"DataDriverError",
|
|
18
|
+
"DataFileNotFoundError",
|
|
19
|
+
"DataFileFormatError",
|
|
20
|
+
"DuplicateModuleError",
|
|
21
|
+
"CaseNotFoundError",
|
|
22
|
+
"ExtractionError",
|
|
23
|
+
"CaseEntry",
|
|
24
|
+
"ParamSet",
|
|
25
|
+
"ExportContext",
|
|
26
|
+
"LayoutMode",
|
|
27
|
+
"OverrideMode",
|
|
28
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Constants and default values for pytest-datadriver."""
|
|
2
|
+
|
|
3
|
+
from pytest_datadriver.models import (
|
|
4
|
+
DataFormat,
|
|
5
|
+
ExportFilter,
|
|
6
|
+
ExportFormat,
|
|
7
|
+
LayoutMode,
|
|
8
|
+
ModuleMatchMode,
|
|
9
|
+
OverrideMode,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Default data file search paths (relative to project root)
|
|
13
|
+
DEFAULT_DATA_PATHS = [
|
|
14
|
+
"TestData/data.yaml",
|
|
15
|
+
"TestData/data.yml",
|
|
16
|
+
"TestData/data.json",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
# Default test directory for extraction
|
|
20
|
+
DEFAULT_TEST_PATH = "tests/"
|
|
21
|
+
|
|
22
|
+
# Default output path for extraction
|
|
23
|
+
DEFAULT_OUTPUT_PATH = "TestData/data.yaml"
|
|
24
|
+
|
|
25
|
+
# CLI option defaults
|
|
26
|
+
DEFAULT_LAYOUT = LayoutMode.CASE
|
|
27
|
+
DEFAULT_MODE = OverrideMode.SMART
|
|
28
|
+
DEFAULT_DATA_FORMAT = DataFormat.AUTO
|
|
29
|
+
DEFAULT_EXPORT_FORMAT = ExportFormat.SIMPLE
|
|
30
|
+
DEFAULT_EXPORT_FILTER = ExportFilter.ALL
|
|
31
|
+
DEFAULT_MODULE_MATCH = ModuleMatchMode.FULLPATH
|
|
32
|
+
|
|
33
|
+
# Mark names used by the library
|
|
34
|
+
MARK_CASE_NO = "caseNo"
|
|
35
|
+
MARK_CASE_NAME = "caseName"
|
|
36
|
+
|
|
37
|
+
# pytest hook marker for skipping items in replace mode
|
|
38
|
+
SKIP_MARKER_NAME = "_skip_by_datadriver"
|
|
39
|
+
|
|
40
|
+
# Case number prefix for auto-generated IDs
|
|
41
|
+
AUTO_CASE_NO_PREFIX = "AUTO-"
|
|
42
|
+
|
|
43
|
+
# CLI option names
|
|
44
|
+
OPTION_DATA_FILE = "--data-file"
|
|
45
|
+
OPTION_DATA_MODE = "--data-mode"
|
|
46
|
+
OPTION_DATA_LAYOUT = "--data-layout"
|
|
47
|
+
OPTION_DATA_FORMAT = "--data-format"
|
|
48
|
+
OPTION_DATA_EXTRACT = "--data-extract"
|
|
49
|
+
OPTION_DATA_OUTPUT = "--data-output"
|
|
50
|
+
OPTION_DATA_EXPORT_FORMAT = "--data-export-format"
|
|
51
|
+
OPTION_DATA_EXPORT_FILTER = "--data-export-filter"
|
|
52
|
+
OPTION_DATA_MODULE_MATCH = "--data-module-match"
|
|
53
|
+
OPTION_TEST_PATH = "--test-path"
|
|
54
|
+
|
|
55
|
+
# Valid choices for CLI options
|
|
56
|
+
VALID_DATA_MODES = [m.value for m in OverrideMode]
|
|
57
|
+
VALID_LAYOUTS = [m.value for m in LayoutMode]
|
|
58
|
+
VALID_FORMATS = [m.value for m in DataFormat]
|
|
59
|
+
VALID_EXPORT_FORMATS = [m.value for m in ExportFormat]
|
|
60
|
+
VALID_EXPORT_FILTERS = [m.value for m in ExportFilter]
|
|
61
|
+
VALID_MODULE_MATCHES = [m.value for m in ModuleMatchMode]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Custom exceptions for pytest-datadriver."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DataDriverError(Exception):
|
|
5
|
+
"""Base exception for all pytest-datadriver errors."""
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DataFileNotFoundError(DataDriverError):
|
|
10
|
+
"""Raised when the specified data file does not exist."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, file_path: str):
|
|
13
|
+
self.file_path = file_path
|
|
14
|
+
super().__init__(f"Data file not found: {file_path}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DataFileFormatError(DataDriverError):
|
|
18
|
+
"""Raised when the data file has invalid YAML/JSON syntax."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, file_path: str, detail: str):
|
|
21
|
+
self.file_path = file_path
|
|
22
|
+
self.detail = detail
|
|
23
|
+
super().__init__(f"Data file format error in '{file_path}': {detail}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DuplicateModuleError(DataDriverError):
|
|
27
|
+
"""Raised when duplicate module filenames are detected in function-first layout with filename matching."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, filename: str, paths: list):
|
|
30
|
+
self.filename = filename
|
|
31
|
+
self.paths = paths
|
|
32
|
+
super().__init__(
|
|
33
|
+
f"Duplicate module filename '{filename}' found in multiple directories: {paths}. "
|
|
34
|
+
f"Use --data-module-match fullpath to resolve."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CaseNotFoundError(DataDriverError):
|
|
39
|
+
"""Raised when a case_no in the data file has no matching test function in code."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, case_no: str):
|
|
42
|
+
self.case_no = case_no
|
|
43
|
+
super().__init__(f"Case '{case_no}' not found in any collected test item")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ExtractionError(DataDriverError):
|
|
47
|
+
"""Raised when AST extraction encounters an error."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, file_path: str, detail: str):
|
|
50
|
+
self.file_path = file_path
|
|
51
|
+
self.detail = detail
|
|
52
|
+
super().__init__(f"Extraction error in '{file_path}': {detail}")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Data exporter factory with auto-detection of file format."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from pytest_datadriver.exporters.base import DataExporter
|
|
6
|
+
from pytest_datadriver.exporters.yaml_exporter import YamlExporter
|
|
7
|
+
from pytest_datadriver.exporters.json_exporter import JsonExporter
|
|
8
|
+
from pytest_datadriver.models import CaseEntry, DataFormat, LayoutMode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_exporter(fmt: DataFormat = DataFormat.AUTO) -> DataExporter:
|
|
12
|
+
"""Create a data exporter for the specified format.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
fmt: Output format (AUTO uses extension detection, YAML or JSON).
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
A DataExporter instance.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ValueError: If the format is not supported.
|
|
22
|
+
"""
|
|
23
|
+
if fmt == DataFormat.YAML:
|
|
24
|
+
return YamlExporter()
|
|
25
|
+
elif fmt == DataFormat.JSON:
|
|
26
|
+
return JsonExporter()
|
|
27
|
+
elif fmt == DataFormat.AUTO:
|
|
28
|
+
# Default to YAML
|
|
29
|
+
return YamlExporter()
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError(f"Unsupported export format: {fmt}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def export_cases(
|
|
35
|
+
cases: List[CaseEntry],
|
|
36
|
+
output_path: str,
|
|
37
|
+
layout: LayoutMode = LayoutMode.CASE,
|
|
38
|
+
fmt: DataFormat = DataFormat.AUTO,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Export cases to a data file, auto-detecting format from extension if AUTO.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
cases: List of CaseEntry objects to export.
|
|
44
|
+
output_path: Path to write the output file.
|
|
45
|
+
layout: Layout mode (case-first or function-first).
|
|
46
|
+
fmt: Output format.
|
|
47
|
+
"""
|
|
48
|
+
if fmt == DataFormat.AUTO:
|
|
49
|
+
lower = output_path.lower()
|
|
50
|
+
if lower.endswith(".json"):
|
|
51
|
+
exporter = JsonExporter()
|
|
52
|
+
else:
|
|
53
|
+
exporter = YamlExporter()
|
|
54
|
+
else:
|
|
55
|
+
exporter = create_exporter(fmt)
|
|
56
|
+
|
|
57
|
+
exporter.export(cases, output_path, layout)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
__all__ = ["DataExporter", "YamlExporter", "JsonExporter", "create_exporter", "export_cases"]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Abstract base class for data exporters."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from pytest_datadriver.models import CaseEntry, LayoutMode
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DataExporter(ABC):
|
|
10
|
+
"""Abstract base class for exporting CaseEntry objects to data files."""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def export(self, cases: List[CaseEntry], output_path: str, layout: LayoutMode = LayoutMode.CASE) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Export a list of CaseEntry objects to a data file.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
cases: List of CaseEntry objects to export.
|
|
19
|
+
output_path: Path to write the output file.
|
|
20
|
+
layout: Layout mode (case-first or function-first).
|
|
21
|
+
"""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def _to_case_first(self, cases: List[CaseEntry]) -> dict:
|
|
25
|
+
"""Convert cases to case-first layout dict."""
|
|
26
|
+
result = {}
|
|
27
|
+
for case in cases:
|
|
28
|
+
entry = {
|
|
29
|
+
"case_name": case.case_name,
|
|
30
|
+
"module": case.module,
|
|
31
|
+
"function": case.function,
|
|
32
|
+
"params": case.params,
|
|
33
|
+
}
|
|
34
|
+
# Add optional fields if present
|
|
35
|
+
if case.fixtures:
|
|
36
|
+
entry["fixtures"] = case.fixtures
|
|
37
|
+
if case.marks:
|
|
38
|
+
entry["marks"] = case.marks
|
|
39
|
+
if case.parametrize_meta:
|
|
40
|
+
entry["parametrize_meta"] = case.parametrize_meta
|
|
41
|
+
if case.source_location:
|
|
42
|
+
entry["source_location"] = case.source_location
|
|
43
|
+
result[case.case_no] = entry
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
def _to_function_first(self, cases: List[CaseEntry]) -> dict:
|
|
47
|
+
"""Convert cases to function-first layout dict."""
|
|
48
|
+
result = {}
|
|
49
|
+
for case in cases:
|
|
50
|
+
key = f"{case.module}::{case.function}"
|
|
51
|
+
if key not in result:
|
|
52
|
+
result[key] = []
|
|
53
|
+
entry = {
|
|
54
|
+
"case_no": case.case_no,
|
|
55
|
+
"case_name": case.case_name,
|
|
56
|
+
"params": case.params,
|
|
57
|
+
}
|
|
58
|
+
if case.fixtures:
|
|
59
|
+
entry["fixtures"] = case.fixtures
|
|
60
|
+
if case.marks:
|
|
61
|
+
entry["marks"] = case.marks
|
|
62
|
+
result[key].append(entry)
|
|
63
|
+
return result
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""JSON data exporter."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from pytest_datadriver.exporters.base import DataExporter
|
|
8
|
+
from pytest_datadriver.models import CaseEntry, LayoutMode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class JsonExporter(DataExporter):
|
|
12
|
+
"""Export test case data to JSON files."""
|
|
13
|
+
|
|
14
|
+
def export(self, cases: List[CaseEntry], output_path: str, layout: LayoutMode = LayoutMode.CASE) -> None:
|
|
15
|
+
"""Export cases to a JSON file."""
|
|
16
|
+
if layout == LayoutMode.CASE:
|
|
17
|
+
data = self._to_case_first(cases)
|
|
18
|
+
else:
|
|
19
|
+
data = self._to_function_first(cases)
|
|
20
|
+
|
|
21
|
+
os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
|
|
22
|
+
|
|
23
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
24
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|