《Effective Python》读书笔记10: 测试与调试
作者: andylin02
学习章节: 第9章 - 测试与调试
关键词: repr调试, unittest, TestCase, setUp, tearDown, Mock, pdb, 测试隔离, 依赖注入
第9章思维导图:7条建议全景
第9章详细笔记:逐条精讲与源代码
本章讲授 Python 中保障代码质量的核心技能:测试与调试。你将学会编写整洁、隔离的单元测试,使用 Mock 对象剥离复杂依赖,以及运用交互式调试器与内存追踪工具快速定位问题。
第75条:使用 repr 字符串输出调试信息
核心:repr() 返回对象的“官方”字符串表示,通常包含类型和定位信息,比直接 print 更适合调试。在自定义类中实现 __repr__ 可让日志和异常输出更加明确。
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x!r}, {self.y!r})"
v = Vector(1, 2)
print(repr(v)) # Vector(1, 2)
print(f"对象: {v!r}") # 对象: Vector(1, 2)
# 调试时使用 repr 而不用 str
values = ["123", 456, Vector(3, 4)]
print(" ".join(repr(val) for val in values))
# '123' 456 Vector(3, 4) ← 类型清晰
对比:str() 面向用户,repr() 面向开发者。所有内置类型都有明确的 repr 实现。
关键收获:为每个类实现 __repr__ 是最小的调试友好投资。
第76条:使用 TestCase 子类验证相关行为
核心:Python 标准库 unittest 提供了 TestCase 基类,通过继承它即可编写结构化、可自动运行的测试。使用 assertEqual、assertTrue、assertRaises 等方法来验证行为。
import unittest
def add(a, b):
return a + b
class TestAdd(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(2, 3), 5)
def test_add_negative(self):
self.assertEqual(add(-1, -2), -3)
def test_add_str(self):
self.assertEqual(add("Hello, ", "World!"), "Hello, World!")
def test_add_invalid(self):
with self.assertRaises(TypeError):
add(1, "2")
if __name__ == "__main__":
unittest.main()
最佳实践:每个测试方法只验证一个行为,方法名以 test_ 开头,让框架自动发现。
第77条:使用 setUp 和 tearDown 隔离测试
核心:测试之间必须完全独立,否则一个测试的副作用会影响另一个。通过重写 setUp(每次测试前运行)和 tearDown(每次测试后运行)来准备和清理环境。
import unittest
import tempfile
import os
class TestFileIO(unittest.TestCase):
def setUp(self):
# 每个测试前创建临时文件
self.temp_dir = tempfile.mkdtemp()
self.temp_file = os.path.join(self.temp_dir, "test.txt")
with open(self.temp_file, "w") as f:
f.write("initial content")
def tearDown(self):
# 每个测试后清理
os.remove(self.temp_file)
os.rmdir(self.temp_dir)
def test_read(self):
with open(self.temp_file) as f:
self.assertEqual(f.read(), "initial content")
def test_write(self):
with open(self.temp_file, "a") as f:
f.write(" appended")
with open(self.temp_file) as f:
self.assertIn("appended", f.read())
模块级隔离:还可使用 setUpModule() 和 tearDownModule() 在整个模块前后执行一次性操作。
关键收获:setUp/tearDown 确保每个测试从相同的清洁起点开始。
第78条:使用 unittest.mock 模拟依赖
核心:当被测代码依赖外部服务(数据库、网络、时钟)时,使用 Mock 对象替换真实依赖,可让测试快速、确定且不产生副作用。unittest.mock.Mock 和 patch 是核心工具。
from unittest.mock import Mock, patch
import unittest
import datetime
def is_weekend():
today = datetime.date.today()
return today.weekday() >= 5 # 5=Saturday, 6=Sunday
class TestWeekend(unittest.TestCase):
@patch('datetime.date')
def test_is_weekend(self, mock_date):
# 让 datetime.date.today() 返回固定日期
mock_date.today.return_value = datetime.date(2023, 7, 15) # 星期六
self.assertTrue(is_weekend())
mock_date.today.return_value = datetime.date(2023, 7, 17) # 星期一
self.assertFalse(is_weekend())
# 更通用的 Mock 用法
def send_email(service, to, subject):
return service.send(to, subject)
class TestEmail(unittest.TestCase):
def test_send_email(self):
mock_service = Mock()
mock_service.send.return_value = True
result = send_email(mock_service, "a@b.com", "Hi")
self.assertTrue(result)
mock_service.send.assert_called_once_with("a@b.com", "Hi")
关键收获:Mock 让测试从外部不确定性中解放,可以验证“调用是否发生、参数是否正确”。
第79条:封装依赖以方便模拟
核心:直接导入外部模块并在函数内使用会令模拟变得困难。更好的做法是将外部依赖作为参数传入(依赖注入),或封装成专门的辅助类,这样测试时可以轻松替换。
# 不易测试:依赖全局模块
import datetime
def greet():
if datetime.datetime.now().hour < 12:
return "Good morning"
return "Good afternoon"
# 易于测试:依赖注入
def greet(now=None):
if now is None:
now = datetime.datetime.now()
if now.hour < 12:
return "Good morning"
return "Good afternoon"
# 测试时注入固定时间
from unittest.mock import Mock
mock_now = Mock(hour=9)
assert greet(mock_now) == "Good morning"
关键收获:好的设计让测试更简单——将难以控制的外部因素变为可传递的参数。
第80条:考虑使用 pdb 交互式调试
核心:pdb 是 Python 内置的交互式调试器。在代码中插入 breakpoint()(Python 3.7+)即可进入 pdb 环境,检查变量、单步执行、修改变量,远比到处 print 高效。
def complex_calculation(x, y):
result = x * y
breakpoint() # 程序在此停止,进入 pdb 控制台
return result / (x - y)
# 运行后,在 pdb 中可以:
# p result # 打印变量
# p x, y
# n # 执行下一行
# c # 继续运行
# q # 退出
常用命令:
n(next):下一行s(step):步入函数c(continue):继续执行p <expr>:打印表达式pp <expr>:美化打印l:列出当前代码位置
关键收获:breakpoint() 是调试的入口,让你像外科手术般检查运行时状态。
第81条:使用 tracemalloc 追踪内存使用和泄漏
核心:Python 3.4+ 提供的 tracemalloc 不仅能测量内存,还能追踪分配位置,是检测内存泄漏和异常大对象的利器。
import tracemalloc
tracemalloc.start()
def leak_func():
global leak_list
leak_list = [b'x' * 1024 for _ in range(1000)]
leak_func()
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('traceback')
for stat in top_stats[:3]:
print(stat)
for line in stat.traceback.format():
print(line)
print("---")
# 比较两个快照,找出增量
snapshot2 = tracemalloc.take_snapshot()
# 进一步分配后对比 snapshot 和 snapshot2 的差异
关键收获:tracemalloc 提供确凿的内存分配证据,精准定位泄漏代码路径。
测试与调试工作流
第9章总结:构建质量安全网
- 调试从
repr开始:为所有类实现__repr__,让输出说话。 - 测试需要结构化:使用
unittest.TestCase,每个方法测试一个行为。 - 隔离是根基:用
setUp/tearDown创建每次独立的环境。 - 模拟是利器:用
Mock和patch替换外部依赖,让测试飞起来。 - 设计为测试服务:将依赖参数化,便于替换。
- 调试利器 pdb:
breakpoint()让你在运行时拥有上帝视角。 - 追踪内存泄漏:
tracemalloc提供精确的内存分配溯源。
掌握这些技能,你将能自信地迭代代码,快速修复缺陷,并维持系统的长期健康。
下一章预告:第10章 - 协作开发
当代码不仅为自己运行,而是融入团队时,就到了全书最后一章:协作开发。
你将学到:
- 如何组织模块与包,设计清晰的 API
- 虚拟环境的配置与依赖管理
- 编写有效的文档字符串
- 为项目构建标准、可重现的开发环境
第10章将为你的 Python 技能树画上完整的句号,让你成为一个善于协作的专业开发者。
本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!
更多推荐

所有评论(0)