11
测试与调试
pytest, pdb, logging
写代码十分钟,调 bug 两小时——这不是段子。会写测试,bug 在本地就被拦住;会用日志,线上问题能复盘。这章学三样:pytest 写测试、pdb 打断点、logging 打日志。
pytest:最省心的测试框架
pytest 比自带的 unittest 简洁得多——不用写类,写个函数、用 assert 就行。装:pip install pytest。
# 文件:test_math.py
def add(a, b):
return a + b
# 测试函数命名必须以 test_ 开头
def test_add():
assert add(1, 2) == 3
assert add(-1, 1) == 0
assert add(0, 0) == 0
# 异常测试:用 pytest.raises
import pytest
def test_raise():
with pytest.raises(ZeroDivisionError):
1 / 0
# 参数化测试:一次喂多组数据
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
])
def test_add_params(a, b, expected):
assert add(a, b) == expected
运行
pytest test_math.py -v
# 看到一堆点(通过)或 F(失败)
调试:print / pdb / logging
# ① print 调试:最快但最土
print("这里的值是:", x)
# ② pdb:命令行断点调试(Python 3.7+ 用 breakpoint())
def buggy():
x = 1
breakpoint() # 程序停在这,进入调试命令行
print(x + 1)
# 进去后:n 下一步,p x 打印变量,c 继续,q 退出
# ③ logging:比 print 正规,能分级、能写文件
import logging
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s")
logging.debug("细节,开发时才看")
logging.info("正常流程")
logging.warning("警告")
logging.error("出错了")
性能分析:先测准,再优化
程序慢了,别凭感觉猜"肯定是网络慢"。先量化,再优化。两个标准库工具:timeit 测小段代码,cProfile 给整个程序画"谁最耗时"的账单。
timeit:精确测一小段代码跑多快
import timeit
# 测"字符串拼接"和"列表 join"哪个快,各跑 10 万次
t1 = timeit.timeit("''.join(str(i) for i in range(100))", number=100000)
t2 = timeit.timeit("''.join([str(i) for i in range(100)])", number=100000)
print(f"生成器版: {t1:.3f}s 列表版: {t2:.3f}s")
# 生成器版: 0.412s 列表版: 0.356s (数字随机器不同,结论是列表略快)
cProfile:给整个程序开账单
# 命令行直接跑:python -m cProfile -s cumtime your_script.py
# -s cumtime 按累计耗时排序,一眼看到谁最费时间
# 也能在代码里这么用
import cProfile
def slow_func():
return sum(i*i for i in range(100000))
cProfile.run("slow_func()")
# ncalls tottime percall cumtime ...
# 100001 0.012 0.000 0.028 slow_func <string>:1
怎么看账单:tottime 是函数自己花的时间(不含它调别人),cumtime 是累计时间(含它调的所有函数)。优化时先盯 cumtime 最高的那几个函数,别瞎改觉得慢的地方。
防坑:别在没 profile 之前就优化
Knuth 名言:"过早优化是万恶之源"。90% 的运行时间花在 10% 的代码上,但你猜不中那 10% 是哪。先 cProfile 找到真正的热点,再动手——否则只是把代码改得更难读,性能毫无变化。
练习:选工具(点开对答案)
问:想知道"解析这个 10MB JSON 慢在哪一步",用 timeit 还是 cProfile?
答:cProfile。它能列出整条调用链每一步的耗时,定位到具体哪个函数;timeit 只适合反复测一小段固定代码的平均耗时,定位不了"程序内部谁最慢"。