浏览知识库目录

Python

测试、调试与代码质量

通过 pytest、Ruff、mypy、traceback 和调试器建立快速而可靠的质量反馈。

测试、调试与代码质量

质量工具的目标不是制造满屏规则,而是缩短反馈周期:测试验证行为,类型检查验证接口关系,静态检查发现常见缺陷,格式化减少无意义差异。


一、学习目标

  • 用 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:

  1. 最后一行是异常类型与消息。
  2. 向上寻找第一处项目代码。
  3. 检查该位置的输入与不变量。
  4. 构造最小复现并写成测试。

断点:

breakpoint()

进入调试器后可查看变量和逐步执行。提交代码前删除临时断点。

日志适合跨请求、后台任务和线上诊断;调试器适合本地暂停与观察。


九、覆盖率的边界

覆盖率能发现未执行代码,不能证明断言正确。优先覆盖:

  • 核心业务规则;
  • 权限和状态变化;
  • 失败与回滚路径;
  • 解析与外部边界;
  • 曾经出现过的缺陷。

不要为了百分比给简单属性写无意义测试,也不要用删除分支的方式“提高覆盖率”。


十、常见错误

  • 测试依赖执行顺序或真实用户目录。
  • 只验证“没有抛异常”,没有断言结果。
  • mock 内部实现细节,重构就大量失败。
  • 为了通过类型检查扩大 Any
  • 自动格式化后不运行测试。

十一、练习与自测

  1. 为未知任务、空标题和数据库约束各写失败测试。
  2. 测试 CLI 的成功、参数错误和任务不存在退出码。
  3. 人为引入一个类型错误和未使用导入,观察工具报告。

自测:

  • 单元测试与集成测试的边界如何确定?
  • fixture 为什么不应隐藏测试的关键输入?
  • 覆盖率不能证明什么?

十二、官方资料

上一篇:并发编程与 asyncio | 下一篇:pyproject、打包与发布