Files
Luong NGUYEN 89e89d4aa3 feat(zh): add Chinese translations in zh/ directory
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.
2026-04-06 23:08:54 +02:00

7.9 KiB
Raw Permalink Blame History

测试指南

本文档说明 Claude How To 的测试基础设施。

概览

项目使用 GitHub Actions 在每次 push 和 pull request 时自动运行测试。测试覆盖:

  • 单元测试:使用 pytest 的 Python 测试
  • 代码质量:使用 Ruff 做 lint 和格式化
  • 安全:使用 Bandit 做漏洞扫描
  • 类型检查:使用 mypy 做静态类型分析
  • 构建验证EPUB 生成测试

在本地运行测试

前置条件

# 安装 uv(快速 Python 包管理器)
pip install uv

# 或者在 macOS 上使用 Homebrew
brew install uv

配置环境

# 克隆仓库
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

运行测试

# 运行所有单元测试
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

# 检查代码格式
ruff format --check scripts/

# 自动修复格式问题
ruff format scripts/

# 运行 lint
ruff check scripts/

# 自动修复 lint 问题
ruff check --fix scripts/

运行安全扫描

# 运行 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

运行类型检查

# 使用 mypy 检查类型
mypy scripts/ --ignore-missing-imports --no-implicit-optional

GitHub Actions 工作流

触发条件

  • 推送到 maindevelop 分支(当 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

# 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.mockpytest-mock
  • 测试边界情况:空输入、None 值、错误情况
  • 保持测试快速:避免 sleep() 和外部 I/O
  • 使用 pytest 标记:例如 @pytest.mark.slow 标记慢测试

Fixtures

常用 fixture 定义在 scripts/tests/conftest.py

# 在测试中使用 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"

覆盖率报告

本地覆盖率

# 生成覆盖率报告
pytest scripts/tests/ --cov=scripts --cov-report=html

# 在浏览器中打开覆盖率报告
open htmlcov/index.html

覆盖率目标

  • 最低覆盖率80%
  • 分支覆盖率:启用
  • 重点区域:核心功能和错误路径

Pre-commit Hooks

项目使用 pre-commit hooks 在每次提交前自动运行检查:

# 安装 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. 测试不稳定:依赖时序或执行顺序的测试

解决方案:

# 使用相同的 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 中配置:

[tool.bandit]
exclude_dirs = ["scripts/tests"]
skips = ["B101"]  # 跳过 assert_used 警告

类型检查太严格

对特定文件放宽类型检查:

# 放在文件顶部
# type: ignore

# 或者针对特定行
some_dynamic_code()  # type: ignore

持续集成最佳实践

  1. 保持测试快速:每个测试最好在 1 秒内完成
  2. 不要测试外部 API:用 mock 替代外部服务
  3. 测试要隔离:每个测试都应独立
  4. 断言要清晰:写 assert x == 5,不要写 assert x
  5. 处理异步测试:使用 @pytest.mark.asyncio
  6. 生成报告:覆盖率、安全扫描、类型检查

资源

贡献测试

提交 PR 时:

  1. 为新功能编写测试
  2. 本地运行测试pytest scripts/tests/ -v
  3. 检查覆盖率pytest scripts/tests/ --cov=scripts
  4. 运行 lintruff check scripts/
  5. 安全扫描bandit -r scripts/ --exclude scripts/tests/
  6. 如果测试变化,更新文档

所有 PR 都必须包含测试!🧪


如果你对测试有问题或疑问,请在 GitHub 上创建 issue 或 discussion。