Add Chinese (Simplified) translations for all documentation, organized under a dedicated zh/ directory that mirrors the English folder structure. Co-authored-by: tanqingkuang <tanqingkuang@users.noreply.github.com> Translations originally contributed by @tanqingkuang in #45. Restructured from *-CN.md suffix pattern into zh/ directory to prevent the EPUB builder (scripts/build_epub.py collect_folder_files) from picking up Chinese files via glob("*.md") inside module folders.
342 lines
7.9 KiB
Markdown
342 lines
7.9 KiB
Markdown
# 测试指南
|
||
|
||
本文档说明 Claude How To 的测试基础设施。
|
||
|
||
## 概览
|
||
|
||
项目使用 GitHub Actions 在每次 push 和 pull request 时自动运行测试。测试覆盖:
|
||
|
||
- **单元测试**:使用 pytest 的 Python 测试
|
||
- **代码质量**:使用 Ruff 做 lint 和格式化
|
||
- **安全**:使用 Bandit 做漏洞扫描
|
||
- **类型检查**:使用 mypy 做静态类型分析
|
||
- **构建验证**:EPUB 生成测试
|
||
|
||
## 在本地运行测试
|
||
|
||
### 前置条件
|
||
|
||
```bash
|
||
# 安装 uv(快速 Python 包管理器)
|
||
pip install uv
|
||
|
||
# 或者在 macOS 上使用 Homebrew
|
||
brew install uv
|
||
```
|
||
|
||
### 配置环境
|
||
|
||
```bash
|
||
# 克隆仓库
|
||
git clone https://github.com/luongnv89/claude-howto.git
|
||
cd claude-howto
|
||
|
||
# 创建虚拟环境
|
||
uv venv
|
||
|
||
# 激活虚拟环境
|
||
source .venv/bin/activate # macOS/Linux
|
||
# 或者
|
||
.venv\Scripts\activate # Windows
|
||
|
||
# 安装开发依赖
|
||
uv pip install -r requirements-dev.txt
|
||
```
|
||
|
||
### 运行测试
|
||
|
||
```bash
|
||
# 运行所有单元测试
|
||
pytest scripts/tests/ -v
|
||
|
||
# 运行带覆盖率的测试
|
||
pytest scripts/tests/ -v --cov=scripts --cov-report=html
|
||
|
||
# 运行指定测试文件
|
||
pytest scripts/tests/test_build_epub.py -v
|
||
|
||
# 运行指定测试函数
|
||
pytest scripts/tests/test_build_epub.py::test_function_name -v
|
||
|
||
# 以 watch 模式运行测试(需要 pytest-watch)
|
||
ptw scripts/tests/
|
||
```
|
||
|
||
### 运行 lint
|
||
|
||
```bash
|
||
# 检查代码格式
|
||
ruff format --check scripts/
|
||
|
||
# 自动修复格式问题
|
||
ruff format scripts/
|
||
|
||
# 运行 lint
|
||
ruff check scripts/
|
||
|
||
# 自动修复 lint 问题
|
||
ruff check --fix scripts/
|
||
```
|
||
|
||
### 运行安全扫描
|
||
|
||
```bash
|
||
# 运行 Bandit 安全扫描
|
||
bandit -c pyproject.toml -r scripts/ --exclude scripts/tests/
|
||
|
||
# 生成 JSON 报告
|
||
bandit -c pyproject.toml -r scripts/ --exclude scripts/tests/ -f json -o bandit-report.json
|
||
```
|
||
|
||
### 运行类型检查
|
||
|
||
```bash
|
||
# 使用 mypy 检查类型
|
||
mypy scripts/ --ignore-missing-imports --no-implicit-optional
|
||
```
|
||
|
||
## GitHub Actions 工作流
|
||
|
||
### 触发条件
|
||
|
||
- 推送到 `main` 或 `develop` 分支(当 `scripts` 有变更时)
|
||
- 向 `main` 提交 Pull Request(当 `scripts` 有变更时)
|
||
- 手动触发 workflow
|
||
|
||
### 作业
|
||
|
||
#### 1. 单元测试(pytest)
|
||
|
||
- **运行环境**:Ubuntu latest
|
||
- **Python 版本**:3.10、3.11、3.12
|
||
- **执行内容**:
|
||
- 从 `requirements-dev.txt` 安装依赖
|
||
- 运行 pytest 并生成覆盖率报告
|
||
- 将覆盖率上传到 Codecov
|
||
- 归档测试结果和 HTML 覆盖率报告
|
||
|
||
**结果**:如果任何测试失败,工作流失败(关键)
|
||
|
||
#### 2. 代码质量(Ruff)
|
||
|
||
- **运行环境**:Ubuntu latest
|
||
- **Python 版本**:3.11
|
||
- **执行内容**:
|
||
- 使用 `ruff format` 检查格式
|
||
- 使用 `ruff check` 运行 lint
|
||
- 报告问题,但不会让整个工作流失败
|
||
|
||
**结果**:非阻塞(仅警告)
|
||
|
||
#### 3. 安全扫描(Bandit)
|
||
|
||
- **运行环境**:Ubuntu latest
|
||
- **Python 版本**:3.11
|
||
- **执行内容**:
|
||
- 扫描安全漏洞
|
||
- 生成 JSON 报告
|
||
- 将报告作为 artifact 上传
|
||
|
||
**结果**:非阻塞(仅警告)
|
||
|
||
#### 4. 类型检查(mypy)
|
||
|
||
- **运行环境**:Ubuntu latest
|
||
- **Python 版本**:3.11
|
||
- **执行内容**:
|
||
- 执行静态类型分析
|
||
- 报告类型不匹配
|
||
- 帮助尽早发现 bug
|
||
|
||
**结果**:非阻塞(仅警告)
|
||
|
||
#### 5. 构建 EPUB
|
||
|
||
- **运行环境**:Ubuntu latest
|
||
- **依赖**:pytest、lint、安全扫描(都必须通过)
|
||
- **执行内容**:
|
||
- 使用 `scripts/build_epub.py` 构建 EPUB
|
||
- 验证 EPUB 是否成功生成
|
||
- 将 EPUB 作为 artifact 上传
|
||
|
||
**结果**:如果构建失败,工作流失败(关键)
|
||
|
||
#### 6. 总结
|
||
|
||
- **运行环境**:Ubuntu latest
|
||
- **依赖**:所有其他作业
|
||
- **执行内容**:
|
||
- 生成工作流总结
|
||
- 列出所有 artifacts
|
||
- 汇总总体状态
|
||
|
||
## 编写测试
|
||
|
||
### 测试结构
|
||
|
||
测试应放在 `scripts/tests/` 中,文件名形如 `test_*.py`:
|
||
|
||
```python
|
||
# scripts/tests/test_example.py
|
||
import pytest
|
||
from scripts.example_module import some_function
|
||
|
||
def test_basic_functionality():
|
||
"""测试 some_function 是否正常工作。"""
|
||
result = some_function("input")
|
||
assert result == "expected_output"
|
||
|
||
def test_error_handling():
|
||
"""测试 some_function 是否能优雅处理错误。"""
|
||
with pytest.raises(ValueError):
|
||
some_function("invalid_input")
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_async_function():
|
||
"""测试异步函数。"""
|
||
result = await async_function()
|
||
assert result is not None
|
||
```
|
||
|
||
### 测试最佳实践
|
||
|
||
- **使用有描述性的名称**:例如 `test_function_returns_correct_value()`
|
||
- **尽量每个测试只做一个断言**:更容易排查失败原因
|
||
- **使用 fixture** 复用初始化逻辑:见 `scripts/tests/conftest.py`
|
||
- **Mock 外部服务**:使用 `unittest.mock` 或 `pytest-mock`
|
||
- **测试边界情况**:空输入、None 值、错误情况
|
||
- **保持测试快速**:避免 `sleep()` 和外部 I/O
|
||
- **使用 pytest 标记**:例如 `@pytest.mark.slow` 标记慢测试
|
||
|
||
### Fixtures
|
||
|
||
常用 fixture 定义在 `scripts/tests/conftest.py`:
|
||
|
||
```python
|
||
# 在测试中使用 fixture
|
||
def test_something(tmp_path):
|
||
"""tmp_path fixture 提供临时目录。"""
|
||
test_file = tmp_path / "test.txt"
|
||
test_file.write_text("content")
|
||
assert test_file.read_text() == "content"
|
||
```
|
||
|
||
## 覆盖率报告
|
||
|
||
### 本地覆盖率
|
||
|
||
```bash
|
||
# 生成覆盖率报告
|
||
pytest scripts/tests/ --cov=scripts --cov-report=html
|
||
|
||
# 在浏览器中打开覆盖率报告
|
||
open htmlcov/index.html
|
||
```
|
||
|
||
### 覆盖率目标
|
||
|
||
- **最低覆盖率**:80%
|
||
- **分支覆盖率**:启用
|
||
- **重点区域**:核心功能和错误路径
|
||
|
||
## Pre-commit Hooks
|
||
|
||
项目使用 pre-commit hooks 在每次提交前自动运行检查:
|
||
|
||
```bash
|
||
# 安装 pre-commit hooks
|
||
pre-commit install
|
||
|
||
# 手动运行 hooks
|
||
pre-commit run --all-files
|
||
|
||
# 跳过某次提交的 hooks(不推荐)
|
||
git commit --no-verify
|
||
```
|
||
|
||
在 `.pre-commit-config.yaml` 中配置的 hooks:
|
||
- Ruff formatter
|
||
- Ruff linter
|
||
- Bandit security scanner
|
||
- YAML validation
|
||
- 文件大小检查
|
||
- 合并冲突检测
|
||
|
||
## 排障
|
||
|
||
### 本地测试通过,但 CI 失败
|
||
|
||
常见原因:
|
||
1. **Python 版本差异**:CI 使用 3.10、3.11、3.12
|
||
2. **依赖缺失**:更新 `requirements-dev.txt`
|
||
3. **平台差异**:路径分隔符、环境变量
|
||
4. **测试不稳定**:依赖时序或执行顺序的测试
|
||
|
||
解决方案:
|
||
```bash
|
||
# 使用相同的 Python 版本测试
|
||
uv python install 3.10 3.11 3.12
|
||
|
||
# 使用干净环境测试
|
||
rm -rf .venv
|
||
uv venv
|
||
uv pip install -r requirements-dev.txt
|
||
pytest scripts/tests/
|
||
```
|
||
|
||
### Bandit 报告误报
|
||
|
||
某些安全警告可能是误报。可在 `pyproject.toml` 中配置:
|
||
|
||
```toml
|
||
[tool.bandit]
|
||
exclude_dirs = ["scripts/tests"]
|
||
skips = ["B101"] # 跳过 assert_used 警告
|
||
```
|
||
|
||
### 类型检查太严格
|
||
|
||
对特定文件放宽类型检查:
|
||
|
||
```python
|
||
# 放在文件顶部
|
||
# type: ignore
|
||
|
||
# 或者针对特定行
|
||
some_dynamic_code() # type: ignore
|
||
```
|
||
|
||
## 持续集成最佳实践
|
||
|
||
1. **保持测试快速**:每个测试最好在 1 秒内完成
|
||
2. **不要测试外部 API**:用 mock 替代外部服务
|
||
3. **测试要隔离**:每个测试都应独立
|
||
4. **断言要清晰**:写 `assert x == 5`,不要写 `assert x`
|
||
5. **处理异步测试**:使用 `@pytest.mark.asyncio`
|
||
6. **生成报告**:覆盖率、安全扫描、类型检查
|
||
|
||
## 资源
|
||
|
||
- [pytest Documentation](https://docs.pytest.org/)
|
||
- [Ruff Documentation](https://docs.astral.sh/ruff/)
|
||
- [Bandit Documentation](https://bandit.readthedocs.io/)
|
||
- [mypy Documentation](https://mypy.readthedocs.io/)
|
||
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
|
||
|
||
## 贡献测试
|
||
|
||
提交 PR 时:
|
||
|
||
1. **为新功能编写测试**
|
||
2. **本地运行测试**:`pytest scripts/tests/ -v`
|
||
3. **检查覆盖率**:`pytest scripts/tests/ --cov=scripts`
|
||
4. **运行 lint**:`ruff check scripts/`
|
||
5. **安全扫描**:`bandit -r scripts/ --exclude scripts/tests/`
|
||
6. **如果测试变化,更新文档**
|
||
|
||
所有 PR 都必须包含测试!🧪
|
||
|
||
---
|
||
|
||
如果你对测试有问题或疑问,请在 GitHub 上创建 issue 或 discussion。
|