跳转到内容

代码质量工程:Ruff + mypy + pytest 三位一体

代码质量工程:Ruff + mypy + pytest 三位一体

学习目标

本章要解决什么问题:一个 10,000+ 行的大型 Python 框架如何在快速迭代中保持代码质量?

读者将学到:

  • Ruff 全量规则集配置与选择性忽略
  • mypy 严格类型检查的启用方式
  • pytest 分层测试体系(单元/集成/基准)

前置知识

本章是 LangChain 特有的工程实践,不涉及通用知识原理。

项目实践

Ruff:全量规则集

LangChain 采用 Ruff 的激进策略——启用 ALL 规则,再选择性关闭不适用的部分:

pyproject.toml
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"C90", # McCabe 复杂度
"COM812", # 与 formatter 冲突
"CPY", # 不要求版权声明
"FIX002", # 允许代码中包含 TODO
"PERF203", # 性能建议,通常不适用
"PLR09", # 参数/语句数量限制
"TD002", # TODO 不需要标注作者
"TD003", # TODO 不需要关联 issue
"ANN401", # 允许使用 Any 类型
"BLE", # 允许捕获盲异常
]

关键配置

配置项说明
line-length100行宽限制
docstring-code-formattrue格式化 docstring 中的代码
conventiongoogleGoogle 风格 docstring
ban-relative-importsall禁止相对导入,强制绝对导入

分区忽略:测试文件和脚本使用 per-file ignores:

[tool.ruff.lint.extend-per-file-ignores]
"tests/*" = ["D1", "S101", "S311", "SLF001", "PLR2004"]
"scripts/*" = ["INP", "T201"]

mypy:严格类型检查

[tool.mypy]
strict = true
enable_error_code = "deprecated"
warn_unreachable = true

关键配置

  • strict = true:启用所有 mypy 严格模式
  • enable_error_code = "deprecated":将使用已弃用 API 视为错误
  • warn_unreachable = true:报告不可达代码(如 return 后的代码)

warn_return_any 暂时关闭(标记为 TODO),等待后续激活完整严格检查。

pytest:分层测试体系

测试配置使用完整的 pytest 插件生态:

[tool.pytest.ini_options]
addopts = "--strict-markers --strict-config --durations=5 --snapshot-warn-unused -vv"
markers = [
"requires: 标记需要特定依赖的测试",
"scheduled: 标记定时运行的测试",
"compile: 标记仅用于编译集成的测试",
"benchmark: 标记基准测试",
]
asyncio_mode = "auto"

测试依赖

工具用途
pytest-asyncio异步测试支持
pytest-xdist并行执行测试
pytest-cov覆盖率报告
pytest-socket网络访问控制
pytest-benchmark基准性能测试
syrupy快照测试
pytest-mockMock 对象
blockbusterIO 阻塞检测
vcrpyHTTP 录制/回放(集成测试)

PR 标题 Lint

PR 标题必须遵循 Conventional Commits 格式:

<type>[optional scope]: <description>
feat(core): add multi-tenant support
fix(langchain): resolve error
docs: update API usage examples

允许的 type 包括:feat(MINOR)、fix(PATCH)、docsstylerefactorperftestbuildcichorerevertreleasehotfix

允许的 scope 预定义:corelangchainlangchain-classicmodel-profilesstandard-teststext-splittersdocs、各 partner 名称、infradepspartners

传统 Commits

CI 中的 pr_lint.yml 检查 PR 标题格式,确保:

  • type 以小写字母开头
  • Breaking changes 使用 ! 后缀(如 feat!: drop support
  • Release commit格式为 release(scope): x.y.z,不允许 body 或 footer

问题与规避

Ruff ALL 规则的维护成本

问题:启用 ALL 规则后,Ruff 版本升级可能引入新规则,导致 CI 突然失败。

对策:ignore 列表中明确排除不适用的规则。新版本引入的规则默认启用,如果不需要则加入 ignore。

mypy 严格模式与 Any 类型

问题strict = true 要求所有类型注解完整,但 LLM 交互中返回类型经常是动态的。

对策:ignore ANN401 允许在必要时使用 Any。对于确实需要 Any 的场景(如动态模型加载),这是可接受的妥协。

测试速度

问题:全量测试在 Monorepo 中可能非常耗时。

对策:CI 通过 check_diffs.yml 变更检测,只运行被修改包的测试。本地开发使用 pytest-xdist 并行执行。集成测试使用 VCR 录制/回放,避免每次运行都调用外部 API。

设计取舍

ALL 规则 vs 精选规则集

方案优势代价
ALL + 选择性忽略不错过任何潜在问题、新规则自动生效需要理解每个忽略的规则为何不适用
精选规则集只启用已知需要的规则可能遗漏有价值的检查

LangChain 选择 ALL + 选择性忽略,因为大型项目需要尽可能全面的代码质量检查,忽略特定规则比遗漏更有可控性。

严格类型检查 vs 渐进式

方案优势代价
严格模式最大程度减少运行时类型错误初期改造成本高、需要处理 Any 类型
渐进式可以逐步启用部分文件类型错误被忽略

LangChain 选择 strict = true,因为类型系统的价值在大型项目中尤为明显。通过 enable_error_code = "deprecated" 还能检测已弃用 API 的使用。

参考来源