系列文章

Python 从入门到精通

第 24 / 36 篇

从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。

  1. 01
    Python 怎么装、怎么用
  2. 02
    变量、数字和字符串
  3. 03
    列表:把一组数据放在一起
  4. 04
    元组、集合和字典
  5. 05
    条件判断:让程序做选择
  6. 06
    循环:重复的事交给程序
  7. 07
    函数:把代码整理成可复用的块
  8. 08
    模块与包:拆分你的程序
  9. 09
    输入、输出与字符串格式化
  10. 10
    文件读写:保存程序的数据
  11. 11
    异常处理:程序出错时怎么办
  12. 12
    基础阶段练习:命令行记账本
  13. 13
    类和对象:面向对象入门
  14. 14
    继承、组合与特殊方法
  15. 15
    迭代器与生成器
  16. 16
    列表推导式与生成器表达式
  17. 17
    装饰器:给函数增加能力
  18. 18
    上下文管理器与 with
  19. 19
    类型标注与 dataclass
  20. 20
    正则表达式:从文本中找规律
  21. 21
    日期、时间与时区
  22. 22
    日志与调试
  23. 23
    虚拟环境与依赖管理
  24. 24
    测试:让修改不再提心吊胆正在阅读
  25. 25
    网络请求:用 Python 调用 API
  26. 26
    网页解析与合规采集
  27. 27
    操作 Excel、CSV 与批量文件
  28. 28
    SQLite:给程序加一个数据库
  29. 29
    数据分析入门:NumPy 与 Pandas
  30. 30
    画图:把数据变得直观
  31. 31
    Flask 入门:做一个小网站
  32. 32
    异步编程:同时处理多项任务
  33. 33
    线程、进程与并发选择
  34. 34
    性能分析与优化
  35. 35
    项目结构、配置与发布
  36. 36
    综合项目:从需求到上线

查看整个系列 →

代码改着改着,最怕的是什么?改 A 把 B 改坏了,自己还不知道。等到上线用户发现,那就晚了。这一篇讲测试:写一段”验证代码正确性”的代码,让每次改动都有自动检查兜底。

测试是什么

测试就是:写代码调用你的函数,断言结果符合预期。跑一遍,绿了说明没问题,红了说明哪里坏了。用 Python 内置的 unittest 或更简洁的 pytest

先看最朴素的”测试”:

def add(a, b):
    return a + b

# 手动验证(不可持续,改一次测一次)
print(add(1, 2))      # 3,肉眼检查
print(add(-1, 1))     # 0

正式的测试框架能把”验证”自动化、可重复。推荐直接用 pytest,简单到不像框架。

安装 pytest 并写第一个测试

pip install pytest

把被测代码和测试分开:

# calc.py(被测代码)
def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError("不能除零")
    return a / b
# test_calc.py(测试文件:文件名以 test_ 开头)
from calc import add, divide

def test_add():
    assert add(1, 2) == 3
    assert add(-1, 1) == 0
    assert add(0, 0) == 0

def test_divide():
    assert divide(10, 2) == 5
    assert divide(7, 2) == 3.5

def test_divide_by_zero():
    import pytest
    with pytest.raises(ValueError):   # 断言"应该抛异常"
        divide(1, 0)

运行:

pytest
# 或指定文件
pytest test_calc.py -v
test_calc.py::test_add PASSED
test_calc.py::test_divide PASSED
test_calc.py::test_divide_by_zero PASSED

3 passed in 0.03s

就这样——assert 一行一个断言,函数名 test_ 开头,pytest 自动发现。没有魔法,没有繁琐的类继承。

断言怎么写

def test_strings():
    assert "南" in "南山小站"
    assert "hello".upper() == "HELLO"
    assert len([1, 2, 3]) == 3

def test_lists():
    result = sorted([3, 1, 2])
    assert result == [1, 2, 3]          # 列表相等
    assert 1 in result

def test_float():
    # 浮点数不能直接 ==,用近似比较
    assert 0.1 + 0.2 == 0.3             # 会失败!0.30000000000000004
    assert abs((0.1 + 0.2) - 0.3) < 1e-9   # 正确姿势
    # 或 pytest.approx
    import pytest
    assert (0.1 + 0.2) == pytest.approx(0.3)

失败时 pytest 会把两边的值都打出来,一眼看出差在哪。

fixture:准备测试环境

多个测试要共用”初始化数据”时,用 fixture(固定装置):

# test_ledger.py
import pytest
from ledger import Ledger

@pytest.fixture
def ledger():
    """每个测试自动获得一个干净的记账本"""
    return Ledger()          # 测试结束自动清理

def test_add_record(ledger):
    ledger.add("吃饭", 12.5)
    assert ledger.total() == 12.5

def test_multiple_records(ledger):
    ledger.add("吃饭", 12.5)
    ledger.add("交通", 5.0)
    assert ledger.total() == 17.5

def test_empty(ledger):
    assert ledger.total() == 0

fixture 的好处:每个测试都从”干净状态”开始,互不影响;数据库、临时文件、网络 mock 都能放 fixture 里。

临时文件测试

测文件读写时,用 pytest 自带的 tmp_path fixture:

# file_ops.py
def save_lines(path, lines):
    with open(path, "w", encoding="utf-8") as f:
        f.writelines(lines)

def load_lines(path):
    with open(path, "r", encoding="utf-8") as f:
        return [line.strip() for line in f]
def test_save_and_load(tmp_path):      # tmp_path:自动创建、自动清理的临时目录
    p = tmp_path / "data.txt"
    save_lines(p, ["第一行n", "第二行n"])
    assert load_lines(p) == ["第一行", "第二行"]

测什么:测试的优先级

不是所有代码都要 100% 覆盖,按性价比来:

  • 必须测:核心业务逻辑(计算、校验、数据处理)——改坏了代价最大
  • 值得测:边界情况(空列表、0、None、超大值)——bug 最爱藏在这
  • 可以测:UI、外部 API 调用(要 mock,成本高)
  • 别测:纯展示代码、一次性脚本

几个经典边界:

def test_edge_cases():
    assert add(0, 0) == 0          # 0 + 0
    assert add(1, 2) == 3          # 正常值
    assert add(-1, 1) == 0         # 负数
    # 除零、空字符串、None 参数……把"最可能出错"的输入都试一遍

TDD:先写测试再写代码

测试驱动开发(TDD)的流程:先写测试(描述”应该怎样”)→ 跑,红 → 写最简代码让它绿 → 重构。好处:

  • 写代码前先想清楚”正确”是什么样
  • 测试就是活的文档,新同事看测试就懂功能
  • 重构有底气——绿了就是没改坏

不用教条化,但”写完功能顺手补个测试“应该成为肌肉记忆。

新手坑

坑 1:测试依赖真实网络/数据库。CI 环境没有网、没有库,测试全红。外部依赖要 mock 或注入。

坑 2:测试之间有顺序依赖。测试 A 改了全局状态影响测试 B——每个测试必须独立可跑。

坑 3:只测”快乐路径”。只测正常输入,边界和异常路径全漏,等于没测。把报错分支也测了。

小结

  • 测试 = 断言函数行为符合预期,pytest 是最简框架:assert + test_ 前缀
  • 浮点数用 pytest.approx;异常用 pytest.raises
  • fixture 准备/清理环境,tmp_path 管临时文件
  • 优先测核心逻辑和边界情况;测试要独立、可重复
  • TDD:先测试后代码,重构有保障

练习

  1. 给之前写的”摄氏转华氏”函数写 pytest 测试:正常值、0 度、负数、边界
  2. 给记账本的”按类别统计”功能写测试:空数据、多类别、重复类别
  3. 思考:为什么浮点数 0.1 + 0.2 != 0.3?这和计算机怎么存小数有关——查一下”浮点数精度”,然后想想测试里该怎么处理