Python
测试、调试与代码质量
通过 pytest、Ruff、mypy、traceback 和调试器建立快速而可靠的质量反馈。
发布于 2026年7月23日
测试、调试与代码质量
质量工具的目标不是制造满屏规则,而是缩短反馈周期:测试验证行为,类型检查验证接口关系,静态检查发现常见缺陷,格式化减少无意义差异。
一、学习目标
- 用 pytest 编写行为测试
- 使用 fixture 和临时目录隔离状态
- 区分单元、集成与端到端测试
- 配置 Ruff 和 mypy
- 使用 traceback、断点和日志定位问题
二、第一个 pytest 测试
import pytest
from study_tasks.models import Task, TaskStatus
def test_new_task_normalizes_title() -> None:
task = Task.new(" 阅读 Python 文档 ")
assert task.title == "阅读 Python 文档"
assert task.status is TaskStatus.TODO
def test_new_task_rejects_blank_title() -> None:
with pytest.raises(ValueError, match="不能为空"):
Task.new(" ")
测试名描述行为,而不是实现函数名。一个测试聚焦一个业务结论,并使用准备、执行、断言的清晰结构。
三、临时数据库 fixture
from pathlib import Path
import pytest
@pytest.fixture
def service(tmp_path: Path) -> TaskService:
repository = TaskRepository(tmp_path / "tasks.db")
return TaskService(repository)
def test_task_lifecycle(service: TaskService) -> None:
created = service.add("编写测试")
assert service.list() == [created]
completed = service.complete(created.id)
assert completed.status is TaskStatus.DONE
service.delete(created.id)
assert service.list() == []
tmp_path 为测试提供独立临时目录,避免污染真实用户数据和测试之间互相影响。
fixture 只放真正共享的准备逻辑;过度抽象会让测试难以理解。
四、测试层次
单元测试
针对纯函数、领域对象和服务规则,依赖使用内存假实现。速度快、失败定位明确。
集成测试
使用真实 SQLite、文件系统或本地 HTTP 服务器,验证边界组件协作。
端到端测试
从 study-tasks 命令运行完整流程,检查退出码、标准输出和数据库结果。
不要把所有测试都伪装成单元测试,也不要让每个小规则都启动完整系统。
五、参数化
@pytest.mark.parametrize(
("raw", "expected"),
[
(" 阅读 ", "阅读"),
("\t测试\n", "测试"),
],
)
def test_normalize_title(raw: str, expected: str) -> None:
assert normalize_title(raw) == expected
多个输入验证同一规则时,参数化比复制测试更清楚。不同业务行为仍应分成独立测试。
六、Ruff
pyproject.toml:
[tool.ruff]
target-version = "py314"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
运行:
ruff check .
ruff format --check .
从较小且高价值的规则集开始。不要一开始启用全部规则再添加大量忽略;忽略必须有具体原因并尽量缩小范围。
七、mypy
[tool.mypy]
python_version = "3.14"
strict = true
packages = ["study_tasks"]
运行:
mypy -p study_tasks
优先为公共接口、领域模型和外部边界补类型。类型错误应通过修正设计解决,而不是用 Any 抹掉。
八、调试
先完整阅读 traceback:
- 最后一行是异常类型与消息。
- 向上寻找第一处项目代码。
- 检查该位置的输入与不变量。
- 构造最小复现并写成测试。
断点:
breakpoint()
进入调试器后可查看变量和逐步执行。提交代码前删除临时断点。
日志适合跨请求、后台任务和线上诊断;调试器适合本地暂停与观察。
九、覆盖率的边界
覆盖率能发现未执行代码,不能证明断言正确。优先覆盖:
- 核心业务规则;
- 权限和状态变化;
- 失败与回滚路径;
- 解析与外部边界;
- 曾经出现过的缺陷。
不要为了百分比给简单属性写无意义测试,也不要用删除分支的方式“提高覆盖率”。
十、常见错误
- 测试依赖执行顺序或真实用户目录。
- 只验证“没有抛异常”,没有断言结果。
- mock 内部实现细节,重构就大量失败。
- 为了通过类型检查扩大
Any。 - 自动格式化后不运行测试。
十一、练习与自测
- 为未知任务、空标题和数据库约束各写失败测试。
- 测试 CLI 的成功、参数错误和任务不存在退出码。
- 人为引入一个类型错误和未使用导入,观察工具报告。
自测:
- 单元测试与集成测试的边界如何确定?
- fixture 为什么不应隐藏测试的关键输入?
- 覆盖率不能证明什么?
十二、官方资料
上一篇:并发编程与 asyncio | 下一篇:pyproject、打包与发布