Course Builder个人学习工作台 · 本地模式
Modern Python:面向有经验开发者的回归路线

Ruff、Pyright 与 pytest 反馈环

目标:能够建立格式、lint、静态类型和行为测试四层反馈,并通过一次有意缺陷证明每个检查确实生效。

四种工具回答不同问题:

  • Ruff formatter:代码布局是否一致;
  • Ruff linter:是否存在未使用导入、可疑模式、过时写法等静态问题;
  • Pyright:标注之间的数据流是否一致;
  • pytest:程序运行时行为是否符合契约。

格式化通过不能证明类型正确,类型检查通过也不能证明业务结果正确。现代工程的关键不是工具数量,而是让它们快速、可重复地组成同一反馈环。

配置实验

第 10 课已经安装 dev dependencies。在 pyproject.toml 加入:

[tool.ruff]
target-version = "py312"
line-length = 100

[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q"

[tool.pyright]
pythonVersion = "3.12"
typeCheckingMode = "standard"
include = ["src", "tests"]

这里先使用 standard:它检查普通类型错误,又不会让尚未完整标注的第三方测试客户端淹没课程反馈。等项目边界稳定后,可以升级到 strict,逐项处理新增诊断,而不是一开始就用全局 ignore。

建立 package:

mkdir -p src/textlab tests
printf '' > src/textlab/__init__.py

将前面课程中的核心函数整理到 src/textlab/core.py:

from __future__ import annotations


def classify(text: str | None, urgent: bool = False) -> str:
    if text is None:
        return "missing"
    cleaned = text.strip()
    if not cleaned:
        return "empty"
    if urgent or "退款" in cleaned:
        return "high"
    return "normal" if len(cleaned) >= 20 else "low"

建立 tests/test_core.py:

from textlab.core import classify


def test_classify_missing_and_empty() -> None:
    assert classify(None) == "missing"
    assert classify("   ") == "empty"


def test_refund_is_high_priority() -> None:
    assert classify("我要退款") == "high"

运行完整反馈环:

uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest

预期输出:四条命令均返回退出码 0,pytest 报告 2 passed。具体耗时和版本行会变化。

为什么先看失败类别

若首次运行有很多错误,不要立刻 # noqa 或关闭 strict:

  1. formatter 失败:先运行 uv run ruff format .,审阅 diff;
  2. import 排序或可修复 lint:运行 uv run ruff check --fix .,仍要审阅;
  3. 类型错误:确认签名是否真的表达接口,不用 Any 粉饰;
  4. pytest 失败:读 assertion diff,分清实现错还是预期错。

引导实验:让门先红再绿

依次制造三种缺陷,每次只改一种:

  1. 加入 import os 但不使用,确认 Ruff 失败;
  2. 在测试里写 classify(123),确认 Pyright 失败;
  3. 把预期 high 改成 low,确认 pytest 失败。

逐个恢复。这个负向实验很重要:一个永远绿色、但看不到目标文件的检查入口没有价值。

独立练习

创建 scripts/check.sh,使任何一步失败时整体立即失败;Windows 用户可以写等价 PowerShell 脚本。

参考实现
#!/usr/bin/env sh
set -eu
uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest
chmod +x scripts/check.sh
./scripts/check.sh

验收:故意加入未使用 import 时脚本返回非零;恢复后返回 0。

测试设计提醒

测试观察契约,而不是复制实现。不要断言私有变量或完整 traceback 文本;优先断言返回值、异常类型、状态码和结构字段。测试应隔离全局可变状态;pytest 每个测试都应能单独运行。

参考资料