import pytest
This commit is contained in:
+245
@@ -0,0 +1,245 @@
|
||||
# 测试框架使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
项目已配置 **pytest** 测试框架,相比你之前的手动测试方法,有以下优势:
|
||||
|
||||
### ✅ 改进点
|
||||
|
||||
| 旧方法 (Jupyter Notebook) | 新方法 (pytest) |
|
||||
|-------------------------|----------------|
|
||||
| 手动运行每个测试用例 | 自动发现和运行所有测试 |
|
||||
| 手动读取和对比文件 | 自动化断言和差异报告 |
|
||||
| 难以定位失败原因 | 清晰的错误堆栈和 diff |
|
||||
| 无法批量运行 | 支持参数化和并行测试 |
|
||||
| 无覆盖率统计 | 自动生成覆盖率报告 |
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 运行所有测试
|
||||
```bash
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
### 2. 运行特定测试文件
|
||||
```bash
|
||||
python -m pytest tests/test_playlist_merge.py -v
|
||||
```
|
||||
|
||||
### 3. 在 VS Code 中运行
|
||||
- 按 `Ctrl+Shift+P`
|
||||
- 输入 "Run Task"
|
||||
- 选择 "运行所有测试" 或其他测试任务
|
||||
|
||||
## 测试文件说明
|
||||
|
||||
### 已创建的测试文件
|
||||
|
||||
```
|
||||
tests/
|
||||
├── __init__.py # 包初始化
|
||||
├── conftest.py # pytest fixtures 和配置
|
||||
├── test_playlist_merge.py # 播放列表合并测试
|
||||
└── README.md # 详细测试指南
|
||||
```
|
||||
|
||||
### 测试用例覆盖
|
||||
|
||||
`test_playlist_merge.py` 包含:
|
||||
|
||||
1. **基础功能测试** (`TestPlaylistMergeBasics`)
|
||||
- `test_load_paths_removes_comments` - 测试路径加载
|
||||
- `test_save_paths_adds_header` - 测试文件保存
|
||||
- `test_empty_playlist_merge` - 测试空播放列表合并
|
||||
|
||||
2. **本地优先测试** (`TestPlaylistMergeLocalPriority`)
|
||||
- 参数化测试 case1-4 的本地优先合并
|
||||
|
||||
3. **远程优先测试** (`TestPlaylistMergeRemotePriority`)
|
||||
- 参数化测试 case1-4 的远程优先合并
|
||||
|
||||
4. **冲突检测测试** (`TestConflictDetection`)
|
||||
- 测试冲突识别和解决
|
||||
|
||||
## 常用命令
|
||||
|
||||
### 基础运行
|
||||
```bash
|
||||
# 运行所有测试
|
||||
pytest
|
||||
|
||||
# 详细输出
|
||||
pytest -v
|
||||
|
||||
# 显示 print 输出
|
||||
pytest -s
|
||||
|
||||
# 只运行特定测试
|
||||
pytest tests/test_playlist_merge.py::TestPlaylistMergeBasics::test_load_paths_removes_comments
|
||||
```
|
||||
|
||||
### 测试筛选
|
||||
```bash
|
||||
# 运行特定 case
|
||||
pytest -k "case_num-1"
|
||||
|
||||
# 运行包含特定关键字的测试
|
||||
pytest -k "local_priority"
|
||||
|
||||
# 运行标记的测试
|
||||
pytest -m unit
|
||||
```
|
||||
|
||||
### 调试和重试
|
||||
```bash
|
||||
# 失败时进入调试器
|
||||
pytest --pdb
|
||||
|
||||
# 只运行上次失败的测试
|
||||
pytest --lf
|
||||
|
||||
# 先运行失败的测试
|
||||
pytest --ff
|
||||
|
||||
# 遇到第一个失败就停止
|
||||
pytest -x
|
||||
```
|
||||
|
||||
### 覆盖率报告
|
||||
```bash
|
||||
# 生成覆盖率报告
|
||||
pytest --cov=app --cov-report=html
|
||||
|
||||
# 在浏览器中查看
|
||||
# 打开 htmlcov/index.html
|
||||
|
||||
# 终端中显示覆盖率
|
||||
pytest --cov=app --cov-report=term
|
||||
```
|
||||
|
||||
## 编写新测试
|
||||
|
||||
### 1. 创建测试文件
|
||||
在 `tests/` 目录下创建 `test_<feature>.py`
|
||||
|
||||
### 2. 编写测试类和函数
|
||||
```python
|
||||
import pytest
|
||||
|
||||
class TestMyFeature:
|
||||
"""测试我的功能"""
|
||||
|
||||
def test_basic_case(self):
|
||||
"""测试基本用例"""
|
||||
result = my_function()
|
||||
assert result == expected_value
|
||||
|
||||
@pytest.mark.parametrize("input,expected", [
|
||||
(1, 2),
|
||||
(2, 4),
|
||||
(3, 6),
|
||||
])
|
||||
def test_multiple_cases(self, input, expected):
|
||||
"""参数化测试多个用例"""
|
||||
assert my_function(input) == expected
|
||||
```
|
||||
|
||||
### 3. 使用 fixtures
|
||||
```python
|
||||
@pytest.fixture
|
||||
def sample_data():
|
||||
"""提供测试数据"""
|
||||
return {"key": "value"}
|
||||
|
||||
def test_with_fixture(sample_data):
|
||||
assert sample_data["key"] == "value"
|
||||
```
|
||||
|
||||
## 与旧测试方法的对比
|
||||
|
||||
### 旧方法 (test.ipynb)
|
||||
```python
|
||||
# 手动循环和对比
|
||||
for i in range(len(test_playlists[1])):
|
||||
with open(test_playlists[1][i], 'r', encoding='utf-8') as f1, \
|
||||
open(target_playlists[i], 'r', encoding='utf-8') as f2:
|
||||
content1 = f1.read()
|
||||
content2 = f2.read()
|
||||
# 手动处理和对比...
|
||||
if content1 == content2:
|
||||
print(f"Test case {i+1} passed.")
|
||||
else:
|
||||
print(f"Test case {i+1} failed.")
|
||||
```
|
||||
|
||||
### 新方法 (pytest)
|
||||
```python
|
||||
@pytest.mark.parametrize("case_num", [1, 2, 3, 4])
|
||||
def test_merge_local_priority(case_num, tmp_path):
|
||||
"""自动运行所有用例,失败时显示详细 diff"""
|
||||
# 加载输入
|
||||
base_text = load_file(f"case{case_num}.m3u")
|
||||
# ... 执行测试
|
||||
result = merge_playlists(base_text, local_text, remote_text)
|
||||
# 自动断言和差异报告
|
||||
assert_playlists_equal(actual_file, expected_file)
|
||||
```
|
||||
|
||||
## 持续集成
|
||||
|
||||
可以将测试集成到 CI/CD 流程:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml (示例)
|
||||
name: Tests
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt
|
||||
- name: Run tests
|
||||
run: pytest --cov=app
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **测试命名** - 使用清晰描述性的名称
|
||||
2. **独立性** - 每个测试应该独立运行
|
||||
3. **快速** - 单元测试应该快速执行
|
||||
4. **明确** - 一个测试只测一个功能点
|
||||
5. **维护** - 定期更新测试用例
|
||||
|
||||
## 故障排除
|
||||
|
||||
### pytest 找不到
|
||||
```bash
|
||||
python -m pip install pytest pytest-cov
|
||||
```
|
||||
|
||||
### 导入错误
|
||||
确保项目根目录在 Python 路径中:
|
||||
```python
|
||||
import sys
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
```
|
||||
|
||||
### 测试发现失败
|
||||
检查 `pytest.ini` 配置和文件命名是否符合规范。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [ ] 为其他模块添加测试 (如 `plex_client.py`, `local_playlist.py`)
|
||||
- [ ] 添加集成测试
|
||||
- [ ] 配置 CI/CD 自动运行测试
|
||||
- [ ] 设置测试覆盖率目标 (如 >80%)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [pytest 官方文档](https://docs.pytest.org/)
|
||||
- [pytest-cov 文档](https://pytest-cov.readthedocs.io/)
|
||||
- 项目内测试: `tests/README.md`
|
||||
Reference in New Issue
Block a user