Python 错误处理与单元测试
Python(04)学习笔记
发布于 2025-04-131. Python 异常处理基础
1.1 基本异常处理结构
try:
# 可能引发异常的代码
result = 10 / 0
except ZeroDivisionError:
# 处理特定类型异常的代码
print("不能除以零!")
else:
# 没有异常时执行的代码
print("计算成功完成")
finally:
# 无论是否发生异常都会执行的代码
print("清理资源...")
1.2 常见的内置异常类型
以下是 Python 中一些最常见的内置异常类型,按类别组织:
基础异常
异常类型 | 描述 | 典型场景 |
---|---|---|
Exception |
常规异常的基类 | 自定义异常的基类 |
SystemExit |
由 sys.exit() 触发 |
程序正常退出 |
KeyboardInterrupt |
用户按下中断键 | 用户按下 Ctrl+C |
数据操作异常
异常类型 | 描述 | 典型场景 |
---|---|---|
TypeError |
类型不匹配 | "2" + 2 (字符串与整数相加) |
ValueError |
值不合适 | int("abc") (非数字字符串转整数) |
AttributeError |
属性不存在 | "hello".missing_method() |
NameError |
变量未定义 | 使用未定义的变量 |
IndexError |
索引超出范围 | my_list[100] (列表越界) |
KeyError |
字典键不存在 | my_dict["不存在的键"] |
文件和 I/O 异常
异常类型 | 描述 | 典型场景 |
---|---|---|
FileNotFoundError |
文件不存在 | open("不存在的文件.txt") |
PermissionError |
权限不足 | 写入只读文件 |
IsADirectoryError |
是目录而非文件 | 尝试作为文件打开目录 |
TimeoutError |
操作超时 | 网络连接超时 |
算术异常
异常类型 | 描述 | 典型场景 |
---|---|---|
ZeroDivisionError |
除以零 | 10 / 0 或 10 % 0 |
OverflowError |
数值溢出 | 超大数运算 |
FloatingPointError |
浮点数计算错误 | 浮点计算精度问题 |
其他重要异常
异常类型 | 描述 | 典型场景 |
---|---|---|
ImportError |
导入模块失败 | import 不存在的模块 |
ModuleNotFoundError |
找不到模块 | 模块路径错误 |
RuntimeError |
一般运行时错误 | 运行时检测到的未分类错误 |
NotImplementedError |
未实现的方法 | 抽象方法没有被子类实现 |
RecursionError |
递归过深 | 无限递归或超出最大递归深度 |
SyntaxError |
语法错误 | 代码不符合 Python 语法 |
IndentationError |
缩进错误 | 不一致的缩进 |
2. 高级异常处理技术
2.1 异常链式传递
Python 3 引入了异常链式传递机制,使用 raise ... from ...
语法:
try:
int("非数字")
except ValueError as e:
raise RuntimeError("数据转换失败") from e
这种方式可以保留原始异常信息,同时提供更高级别的错误上下文。
2.2 自定义异常类
为特定应用场景创建自定义异常可以使代码更具表达力:
class ConfigError(Exception):
"""配置文件相关错误的基类"""
pass
class ConfigFileNotFoundError(ConfigError):
"""配置文件不存在时触发"""
pass
class ConfigParseError(ConfigError):
"""配置文件格式错误时触发"""
pass
2.3 上下文管理器与异常处理
使用 with
语句可以简化资源管理和异常处理:
with open("data.txt", "r") as file:
content = file.read()
# 文件会自动关闭,即使发生异常
3. Python 单元测试基础
3.1 unittest 框架
unittest
是 Python 标准库中的单元测试框架:
import unittest
def add(a, b):
return a + b
class TestAddFunction(unittest.TestCase):
def test_add_integers(self):
self.assertEqual(add(1, 2), 3)
def test_add_floats(self):
self.assertAlmostEqual(add(1.1, 2.2), 3.3)
def test_add_strings(self):
self.assertEqual(add("hello ", "world"), "hello world")
if __name__ == "__main__":
unittest.main()
3.2 pytest 框架
pytest
是一个第三方测试框架,提供了更简洁的语法:
# test_add.py
def add(a, b):
return a + b
def test_add_integers():
assert add(1, 2) == 3
def test_add_floats():
assert abs(add(1.1, 2.2) - 3.3) < 0.00001
def test_add_strings():
assert add("hello ", "world") == "hello world"
运行测试只需命令 pytest test_add.py
。
3.3 常用断言方法
无论使用 unittest
还是 pytest
,理解各种断言方法对编写有效的测试至关重要:
断言方法 | 用途 | 示例 |
---|---|---|
assertEqual |
判断两个值相等 | self.assertEqual(1+1 2) |
assertNotEqual |
判断两个值不相等 | self.assertNotEqual(1+1 3) |
assertTrue |
判断表达式为真 | self.assertTrue(1 < 2) |
assertFalse |
判断表达式为假 | self.assertFalse(1 > 2) |
assertRaises |
判断是否引发指定异常 | self.assertRaises(ZeroDivisionError lambda: 1/0) |
assertIn |
判断元素在容器中 | self.assertIn(1 [1 2 3]) |
assertNotIn |
判断元素不在容器中 | self.assertNotIn(4 [1 2 3]) |
assertIsNone |
判断对象为 None | self.assertIsNone(None) |
assertIsNotNone |
判断对象不为 None | self.assertIsNotNone(0) |
4. 错误处理与单元测试的结合
4.1 测试异常处理
import unittest
def divide(a, b):
if b == 0:
raise ValueError("被除数不能为零")
return a / b
class TestDivideFunction(unittest.TestCase):
def test_divide_normal(self):
self.assertEqual(divide(10, 2), 5)
def test_divide_zero(self):
# 验证是否正确引发异常
with self.assertRaises(ValueError) as context:
divide(10, 0)
# 验证异常消息
self.assertIn("被除数不能为零", str(context.exception))
4.2 使用 mock 进行单元测试
from unittest.mock import Mock, patch
import unittest
def get_user_data(user_id):
# 假设这个函数调用外部 API
response = fetch_from_api(f"/users/{user_id}")
return response.json()
class TestGetUserData(unittest.TestCase):
@patch('__main__.fetch_from_api')
def test_get_user_data(self, mock_fetch):
# 创建一个模拟 response 对象
mock_response = Mock()
mock_response.json.return_value = {"id": 1, "name": "测试用户"}
mock_fetch.return_value = mock_response
# 测试函数
result = get_user_data(1)
# 验证结果
self.assertEqual(result, {"id": 1, "name": "测试用户"})
# 验证 fetch_from_api 被正确调用
mock_fetch.assert_called_once_with("/users/1")
4.3 测试覆盖率
pip install coverage
coverage run -m pytest test_module.py
coverage report -m
这会生成一个测试覆盖率报告,显示每个模块的覆盖率以及未被测试覆盖的代码行。