文章5:Python中的异常处理和测试
目标
掌握Python的异常处理机制,学会编写健壮的错误处理代码,并使用单元测试和Mock技术确保代码质量。
一、异常处理基础
1. try-except-finally
结构
Python通过try-except
结构捕获和处理异常,确保程序在错误发生时不会崩溃,并提供清理操作。
示例:除法运算与文件读取
try:
# 可能引发异常的代码
result = 10 / 0
with open("nonexistent.txt", "r") as f:
content = f.read()
except ZeroDivisionError as e:
print(f"除以零错误: {e}") # 处理除零错误
except FileNotFoundError as e:
print(f"文件未找到: {e}") # 处理文件不存在错误
except Exception as e:
print(f"未知错误: {e}") # 捕获其他所有异常
else:
print("操作成功,无异常发生") # 仅在无异常时执行
finally:
print("清理资源(如关闭文件或释放资源)") # 总是执行
2. 自定义异常类
通过继承Exception
或其子类,可以创建自定义异常,提高错误信息的可读性。
示例:自定义验证异常
class InvalidAgeError(Exception):
"""当年龄无效时抛出的异常"""
def __init__(self, age):
self.age = age
self.message = f"年龄 {age} 不合法(必须为1-120之间的整数)"
super().__init__(self.message)
def validate_age(age):
if not 1 <= age <= 120:
raise InvalidAgeError(age)
return True
try:
validate_age(150) # 触发异常
except InvalidAgeError as e:
print(e) # 输出:"年龄 150 不合法(必须为1-120之间的整数)"
二、单元测试框架
1. unittest
框架
Python内置的单元测试框架,通过继承TestCase
类定义测试用例。
示例:计算器测试
import unittest
class Calculator:
def add(self, a, b):
return a + b
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calc = Calculator() # 每个测试用例前初始化对象
def test_add_positive(self):
self.assertEqual(self.calc.add(2, 3), 5)
def test_add_negative(self):
self.assertEqual(self.calc.add(-1, -1), -2)
def tearDown(self):
del self.calc # 每个测试用例后清理资源
if __name__ == "__main__":
unittest.main()
2. pytest
框架
更简洁的第三方测试框架,支持参数化、fixture和更灵活的语法。
示例:参数化测试
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, -1) == -2
# 参数化测试:测试多个输入组合
import pytest
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(0, 0, 0),
(-5, 3, -2)
])
def test_add_param(a, b, expected):
assert add(a, b) == expected
运行命令:
pytest test_file.py
三、Mock对象与隔离测试
1. unittest.mock
模块
通过Mock
和patch
模拟外部依赖,确保测试独立性。
示例:模拟API调用
from unittest.mock import patch
def fetch_data_from_api():
# 假设这是一个外部API调用
import requests
return requests.get("https://api.example.com/data").json()
def get_user_data():
response = fetch_data_from_api()
return response.get("user", {})
# 测试时模拟API返回
class TestUserData(unittest.TestCase):
@patch("module.requests.get")
def test_get_user_data(self, mock_get):
mock_response = {"user": {"id": 1, "name": "Alice"}}
mock_get.return_value.json.return_value = mock_response
result = get_user_data()
self.assertEqual(result["name"], "Alice")
2. pytest-mock
插件
更简洁的Mock工具,适用于pytest。
示例:模拟文件写入
def write_to_file(content):
with open("output.txt", "w") as f:
f.write(content)
def test_write_to_file(mocker):
mock_open = mocker.mock_open()
mocker.patch("builtins.open", mock_open)
write_to_file("Hello, Test!")
mock_open.assert_called_once_with("output.txt", "w")
mock_open().write.assert_called_once_with("Hello, Test!")
四、综合示例:用户注册系统
# app.py
class UserRegistrationError(Exception):
pass
def register_user(username, email):
if not username or not email:
raise UserRegistrationError("用户名或邮箱不能为空")
# 模拟数据库操作
print(f"用户 {username} 注册成功!")
# tests/test_app.py (使用pytest)
from app import register_user
import pytest
def test_register_user_success():
register_user("alice", "alice@example.com") # 正常情况
def test_register_user_empty_username():
with pytest.raises(UserRegistrationError):
register_user("", "bob@example.com")
def test_register_user_empty_email():
with pytest.raises(UserRegistrationError):
register_user("bob", "")
练习题
- 编写一个函数,计算两个数的商,并使用
try-except
处理除零错误。 - 定义一个自定义异常
InvalidURLException
,并在验证URL格式时抛出。 - 使用
unittest
为一个BankAccount
类编写测试,覆盖存款、取款和余额查询功能。 - 使用
pytest-mock
模拟一个依赖外部API的函数,并测试其返回值。
总结
通过掌握异常处理、单元测试和Mock技术,你可以编写更健壮、可维护的代码:
- 异常处理:确保程序在错误时优雅地恢复或提示用户。
- 单元测试:验证代码逻辑的正确性,减少回归风险。
- Mock技术:隔离依赖,提高测试效率和独立性。
继续实践这些技术,你将能够构建高质量的Python应用程序!