Python 教程 / 错误处理 / 调试和测试

调试和测试 (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 在线代码编辑器
📝 运行结果