Skip to content

Commit 1ed2563

Browse files
committed
add
1 parent 84cd6a9 commit 1ed2563

File tree

6 files changed

+495
-504
lines changed

6 files changed

+495
-504
lines changed

development/COMPLETE_SOLUTION.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# ✅ 完整解决方案:自动测试发现
2+
3+
## 🎯 问题和解决方案
4+
5+
### 问题
6+
Pytest 对测试发现有严格的命名要求:
7+
- 文件名必须是 `test_*.py``*_test.py`
8+
- 函数名必须以 `test_` 开头
9+
10+
用户希望使用 `@evaluation_test` 装饰器后,无论如何命名都能被发现。
11+
12+
### 解决方案
13+
14+
#### ✅ 函数名:完全自动处理(无需任何操作)
15+
使用 `@evaluation_test` 装饰的函数会自动注册正确的测试名称。
16+
17+
```python
18+
# 任何函数名都可以!
19+
@evaluation_test(...)
20+
async def my_custom_eval(row: EvaluationRow) -> EvaluationRow:
21+
# 自动注册为 test_my_custom_eval
22+
...
23+
```
24+
25+
#### ✅ 文件名:三种方式
26+
27+
**方式 1:明确指定文件(最简单)**
28+
```bash
29+
pytest path/to/any_filename.py
30+
```
31+
32+
**方式 2:使用标准命名**
33+
```bash
34+
# 文件名: test_*.py
35+
pytest # 自动发现
36+
```
37+
38+
**方式 3:使用 --ep-discover-all 标志**
39+
```bash
40+
pytest --ep-discover-all # 发现所有 .py 文件中的测试
41+
```
42+
43+
## 📋 实现的代码修改
44+
45+
### 1. 函数名自动注册
46+
47+
**文件**: `eval_protocol/pytest/evaluation_test.py`
48+
49+
```python
50+
# 在 decorator 返回之前自动注册
51+
original_name = test_func.__name__
52+
if not original_name.startswith('test_'):
53+
import sys
54+
frame = sys._getframe(1)
55+
caller_globals = frame.f_globals
56+
test_name = f'test_{original_name}'
57+
if test_name not in caller_globals:
58+
caller_globals[test_name] = dual_mode_wrapper
59+
```
60+
61+
**工作原理**
62+
- 使用 `sys._getframe(1)` 获取调用者的全局命名空间
63+
- 在命名空间中注册 `test_{function_name}` 别名
64+
- Pytest 扫描模块时发现这个别名
65+
66+
### 2. Wrapper 名称修正
67+
68+
**文件**: `eval_protocol/pytest/parameterize.py`
69+
70+
```python
71+
# 确保 wrapper 的 __name__ 以 test_ 开头
72+
original_name = test_func.__name__
73+
if not original_name.startswith('test_'):
74+
wrapper.__name__ = f'test_{original_name}'
75+
```
76+
77+
**文件**: `eval_protocol/pytest/dual_mode_wrapper.py`
78+
79+
```python
80+
# 确保 dual_mode_wrapper 的 __name__ 以 test_ 开头
81+
original_name = test_func.__name__
82+
if not original_name.startswith('test_'):
83+
dual_mode_wrapper.__name__ = f'test_{original_name}'
84+
```
85+
86+
### 3. 文件名配置选项
87+
88+
**文件**: `eval_protocol/pytest/plugin.py`
89+
90+
```python
91+
def pytest_addoption(parser) -> None:
92+
group = parser.getgroup("eval-protocol")
93+
group.addoption(
94+
"--ep-discover-all",
95+
action="store_true",
96+
default=False,
97+
help=(
98+
"Discover @evaluation_test in all Python files, "
99+
"not just test_*.py files."
100+
),
101+
)
102+
103+
def pytest_configure(config) -> None:
104+
# 启用发现所有 .py 文件
105+
if config.getoption("--ep-discover-all", default=False):
106+
config.option.python_files = ["*.py"]
107+
```
108+
109+
## 🧪 验证和测试
110+
111+
### 测试文件
112+
- `tests/test_auto_discovery_simple.py` - 验证函数名自动注册
113+
- `examples/auto_discovery_example.py` - 标准命名示例
114+
- `examples/my_evaluation.py` - 非标准命名示例
115+
116+
### 验证结果
117+
118+
```bash
119+
# 1. 非标准文件名 + 非标准函数名
120+
$ pytest examples/my_evaluation.py --collect-only -v
121+
collected 1 item
122+
<Coroutine test_custom_evaluation[rows(len=1)]>
123+
124+
# 2. 运行测试
125+
$ pytest examples/my_evaluation.py -v
126+
============================== 1 passed in 0.08s =============================== ✅
127+
128+
# 3. 标准命名
129+
$ pytest examples/auto_discovery_example.py --collect-only -v
130+
collected 3 items
131+
<Coroutine test_math_evaluation[rows(len=1)]>
132+
<Coroutine test_greeting_evaluation[rows(len=1)]>
133+
<Coroutine test_coding_task_evaluation[rows(len=1)]>
134+
```
135+
136+
## 📚 使用示例
137+
138+
### 示例 1:完全自由的命名
139+
140+
```python
141+
# 文件: evals/math.py
142+
from eval_protocol.pytest import evaluation_test
143+
from eval_protocol.models import EvaluationRow, EvaluateResult
144+
145+
@evaluation_test(
146+
input_rows=[[EvaluationRow(messages=[{"role": "user", "content": "2+2"}])]]
147+
)
148+
async def evaluate_addition(row: EvaluationRow) -> EvaluationRow:
149+
row.evaluation_result = EvaluateResult(score=1.0)
150+
return row
151+
```
152+
153+
运行:
154+
```bash
155+
pytest evals/math.py -v
156+
```
157+
158+
### 示例 2:使用标准命名
159+
160+
```python
161+
# 文件: tests/test_math_eval.py
162+
@evaluation_test(...)
163+
async def test_addition(row: EvaluationRow) -> EvaluationRow:
164+
...
165+
```
166+
167+
运行:
168+
```bash
169+
pytest tests/ # 自动发现所有 test_*.py
170+
```
171+
172+
### 示例 3:混合使用
173+
174+
```python
175+
# 文件: test_my_evals.py(标准文件名)
176+
@evaluation_test(...)
177+
async def math_accuracy_check(row: EvaluationRow) -> EvaluationRow:
178+
# 函数名不标准也没问题
179+
...
180+
```
181+
182+
运行:
183+
```bash
184+
pytest # 自动发现
185+
```
186+
187+
## 🎁 特性总结
188+
189+
| 特性 | 状态 | 说明 |
190+
|------|------|------|
191+
| 函数名自由 || 任何函数名都能被发现 |
192+
| 文件名灵活 || 支持明确指定或使用标志 |
193+
| 零配置 || 函数名完全自动处理 |
194+
| 向后兼容 || 不影响现有代码 |
195+
| 无警告 || 静默自动处理 |
196+
197+
## 📖 文档
198+
199+
- `development/auto_test_discovery.md` - 技术实现细节
200+
- `development/file_and_function_naming.md` - 文件名和函数名处理指南
201+
- `development/FINAL_SUMMARY.md` - 功能总结
202+
- `development/COMPLETE_SOLUTION.md` - 本文档
203+
204+
## 🚀 推荐用法
205+
206+
### 最简单:明确指定文件
207+
```bash
208+
pytest path/to/your_file.py
209+
```
210+
- ✅ 任何文件名都可以
211+
- ✅ 任何函数名都可以
212+
- ✅ 无需额外配置
213+
214+
### 最传统:使用标准命名
215+
```bash
216+
# 文件: test_*.py
217+
# 函数: test_* 或任意名称
218+
pytest
219+
```
220+
- ✅ 自动发现
221+
- ✅ 团队熟悉的方式
222+
223+
### 最灵活:使用 --ep-discover-all
224+
```bash
225+
pytest --ep-discover-all
226+
```
227+
- ✅ 发现所有文件中的测试
228+
- ✅ 适合大量非标准命名文件
229+
230+
## ✨ 总结
231+
232+
现在使用 `@evaluation_test` 装饰器:
233+
234+
1. **函数名**:完全自由,自动处理 ✅
235+
2. **文件名**
236+
- 明确指定:`pytest your_file.py`
237+
- 标准命名:`test_*.py` 自动发现 ✅
238+
- 或使用:`pytest --ep-discover-all`
239+
240+
**用户只需要使用 `@evaluation_test`,其他都自动完成!** 🎉
241+

0 commit comments

Comments
 (0)