Python 从入门到精通
第 24 / 36 篇
从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。
-
01
Python 怎么装、怎么用 -
02
变量、数字和字符串 -
03
列表:把一组数据放在一起 -
04
元组、集合和字典 -
05
条件判断:让程序做选择 -
06
循环:重复的事交给程序 -
07
函数:把代码整理成可复用的块 -
08
模块与包:拆分你的程序 -
09
输入、输出与字符串格式化 -
10
文件读写:保存程序的数据 -
11
异常处理:程序出错时怎么办 -
12
基础阶段练习:命令行记账本 -
13
类和对象:面向对象入门 -
14
继承、组合与特殊方法 -
15
迭代器与生成器 -
16
列表推导式与生成器表达式 -
17
装饰器:给函数增加能力 -
18
上下文管理器与 with -
19
类型标注与 dataclass -
20
正则表达式:从文本中找规律 -
21
日期、时间与时区 -
22
日志与调试 -
23
虚拟环境与依赖管理 -
24
测试:让修改不再提心吊胆正在阅读 -
25
网络请求:用 Python 调用 API -
26
网页解析与合规采集 -
27
操作 Excel、CSV 与批量文件 -
28
SQLite:给程序加一个数据库 -
29
数据分析入门:NumPy 与 Pandas -
30
画图:把数据变得直观 -
31
Flask 入门:做一个小网站 -
32
异步编程:同时处理多项任务 -
33
线程、进程与并发选择 -
34
性能分析与优化 -
35
项目结构、配置与发布 -
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:先测试后代码,重构有保障
练习
- 给之前写的”摄氏转华氏”函数写 pytest 测试:正常值、0 度、负数、边界
- 给记账本的”按类别统计”功能写测试:空数据、多类别、重复类别
- 思考:为什么浮点数
0.1 + 0.2 != 0.3?这和计算机怎么存小数有关——查一下”浮点数精度”,然后想想测试里该怎么处理