调试和测试 (Debugging and Testing)
调试和测试是软件开发中至关重要的环节。本章将学习Python提供的断言、单元测试、文档测试、pdb调试器和日志系统等工具。
断言 (assert)
断言是代码中的自我检查点,用于验证程序状态是否符合预期。如果断言失败,会抛出 AssertionError。
┌─────────────────────────────────────────────────────────────────┐
│ assert 语句执行流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ assert 条件, "错误信息" │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 条件为 True → 继续执行下一行代码 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 条件为 False → 抛出 AssertionError │ │
│ │ 程序停止或被捕获 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 用途: │
│ • 开发时检查"这应该为真" │
│ • 调试时验证中间结果 │
│ • 文档化代码假设 │
│ │
└─────────────────────────────────────────────────────────────────┘
基本用法
Python
# 基本断言
assert 1 + 1 == 2, "加法应该等于2"
# 带错误信息的断言
def divide(a, b):
assert b != 0, "除数不能为零"
return a / b
assert divide(10, 2) == 5
# 断言函数返回值
def add(a, b):
return a + b
assert add(1, 2) == 3
assert add(0, 0) == 0
assert add(-1, 1) == 0
print("所有断言通过!")
断言 vs 异常处理
| 特性 | assert | if + raise |
|---|---|---|
| 用途 | 检查程序员的假设/错误 | 处理用户输入/运行时错误 |
| 可禁用 | python -O 优化模式会跳过 | 不能禁用 |
| 抛出异常 | AssertionError | 自定义异常类型 |
| 使用场景 | 开发/测试时验证 | 生产环境错误处理 |
单元测试 (unittest)
unittest 是Python标准库提供的单元测试框架,灵感来自Java的JUnit。
┌─────────────────────────────────────────────────────────────────┐
│ unittest 基本结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ import unittest │
│ │
│ class TestMath(unittest.TestCase): ← 测试类继承 TestCase │
│ │
│ def test_add(self): ← test_ 开头的方法 │
│ self.assertEqual(add(1,2), 3) │
│ │
│ def test_divide(self): │
│ self.assertRaises(ZeroDivisionError, divide, 1, 0) │
│ │
│ if __name__ == "__main__": ← 直接运行测试 │
│ unittest.main() │
│ │
│ 运行结果: │
│ Ran 2 tests in 0.001s │
│ OK │
│ │
└─────────────────────────────────────────────────────────────────┘
常用断言方法
Python
import unittest
class TestMath(unittest.TestCase):
# assertEqual - 断言相等
def test_add(self):
self.assertEqual(1 + 1, 2)
self.assertEqual("hello" + " world", "hello world")
# assertTrue / assertFalse
def test_boolean(self):
self.assertTrue(1 < 2)
self.assertFalse(1 > 2)
# assertRaises - 断言抛出异常
def test_exception(self):
with self.assertRaises(ZeroDivisionError):
x = 1 / 0
# assertIn - 断言包含
def test_contains(self):
self.assertIn("a", "abc")
self.assertIn(1, [1, 2, 3])
# assertIsNone / assertIsNotNone
def test_none(self):
self.assertIsNone(None)
self.assertIsNotNone("value")
# assertAlmostEqual - 浮点数比较
def test_float(self):
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=5)
if __name__ == "__main__":
unittest.main(verbosity=2)
setUp 和 tearDown
Python
import unittest
class TestCalculator(unittest.TestCase):
# 每个测试方法前执行
def setUp(self):
print("setUp: 初始化计算器")
self.calc = Calculator()
# 每个测试方法后执行
def tearDown(self):
print("tearDown: 清理资源")
def test_add(self):
self.assertEqual(self.calc.add(1, 2), 3)
def test_subtract(self):
self.assertEqual(self.calc.subtract(5, 3), 2)
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
文档测试 (doctest)
doctest 允许在文档字符串中编写测试用例,通过运行示例代码来验证其正确性。
Python
def add(a, b):
"""
返回两个数的和
>>> add(1, 2)
3
>>> add(-1, 1)
0
>>> add(0, 0)
0
"""
return a + b
def factorial(n):
"""
计算 n 的阶乘
>>> factorial(0)
1
>>> factorial(5)
120
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n 不能为负数
"""
if n < 0:
raise ValueError("n 不能为负数")
if n <= 1:
return 1
return n * factorial(n - 1)
# 运行文档测试
if __name__ == "__main__":
import doctest
doctest.testmod(verbose=True)
doctest 格式
•
• 下一行是期望的输出
• 空行结束一个测试用例
•
•
>>> 后面是输入的代码• 下一行是期望的输出
• 空行结束一个测试用例
•
... 表示省略部分输出
pdb 调试器
pdb 是Python的交互式源代码调试器,类似于GDB。
┌─────────────────────────────────────────────────────────────────┐
│ pdb 常用命令 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 启动调试: │
│ • python -m pdb script.py # 启动 pdb │
│ • import pdb; pdb.set_trace() # 在代码中设置断点 │
│ │
│ 断点控制: │
│ • b / break # 显示/设置断点 │
│ • c / continue # 继续执行到下一个断点 │
│ • n / next # 执行下一行(不进入函数) │
│ • s / step # 执行下一行(进入函数) │
│ │
│ 信息查看: │
│ • p / print # 打印变量值 │
│ • pp # 格式化打印 │
│ • l / list # 查看代码 │
│ • w / where # 查看调用栈 │
│ │
│ 其他: │
│ • q / quit # 退出调试 │
│ • h / help # 帮助 │
│ • a / args # 查看参数 │
│ │
└─────────────────────────────────────────────────────────────────┘
Python
# 在代码中设置断点
import pdb
def buggy_function(data):
result = 0
for i in range(10):
pdb.set_trace() # 设置断点,程序会在此处暂停
result += data[i] * i # 可能出错
return result
# 使用 pdb 命令调试:
# n (next) - 执行下一行
# p data - 打印 data 变量
# p i - 打印 i 变量
# c (continue) - 继续执行
# q (quit) - 退出调试
# data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# buggy_function(data)
IDE 调试器
在实际开发中,更推荐使用IDE(如PyCharm、VS Code)的图形化调试器,它们提供:
- 可视化的断点管理
- 变量监视面板
- 调用栈窗口
- 单步调试按钮
日志 (logging)
logging 是Python标准库的日志模块,比 print 更适合生产环境的调试信息记录。
┌─────────────────────────────────────────────────────────────────┐
│ 日志级别 (从低到高) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ DEBUG - 详细的调试信息,开发时使用 │
│ INFO - 一般信息,确认程序按预期工作 │
│ WARNING - 警告,可能有问题但不影响运行 │
│ ERROR - 错误,某个功能失败 │
│ CRITICAL - 严重错误,程序可能无法继续运行 │
│ │
│ 默认级别是 WARNING,只会显示 WARNING 及以上 │
│ │
└─────────────────────────────────────────────────────────────────┘
Python
import logging
# 基本配置
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
# 创建 logger
logger = logging.getLogger(__name__)
# 不同级别的日志
logger.debug("这是一条调试信息")
logger.info("这是一条普通信息")
logger.warning("这是一条警告")
logger.error("这是一条错误")
logger.critical("这是一条严重错误")
日志配置示例
Python
import logging
# 配置日志同时输出到控制台和文件
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[
logging.StreamHandler(), # 输出到控制台
logging.FileHandler("app.log") # 输出到文件
]
)
logger = logging.getLogger(__span class="string">"my_app"")
# 在不同模块中使用
def process_data(data):
logger.info(f"开始处理数据: {data}")
try:
result = sum(data)
logger.info(f"处理完成,结果: {result}")
return result
except Exception as e:
logger.error(f"处理失败: {e}")
raise
知识测验
问题1:以下代码的输出是什么?
assert 1 + 1 == 3, "错误"
print("下一行")
问题2:unittest 中哪个方法用于测试抛出异常?
课后练习
练习1:编写单元测试
为一个字符串处理函数编写完整的单元测试:
- 函数 reverse_string(s) - 反转字符串
- 编写测试用例覆盖:正常字符串、空字符串、特殊字符
- 使用 setUp/tearDown
- 使用 assertRaises 测试异常情况
练习2:使用日志系统
实现一个计算器类并添加日志:
- 创建 Calculator 类,包含加减乘除方法
- 使用 logging 记录每次运算
- 不同操作使用不同级别(除法错误用 ERROR)
- 配置日志同时输出到控制台和文件
练习3:文档测试
为阶乘函数编写文档测试:
- 函数 factorial(n) 计算阶乘
- 在 docstring 中编写 doctest 用例
- 包含正常情况和异常情况
- 运行 doctest 验证
🐍 Python 在线代码编辑器
📝 运行结果