8. pytest mock与patch
8.1 pytest-mock基础使用
pytest-mock插件提供了简洁的mock功能,基于unittest.mock。
pytest-mock基础示例
python
# test_pytest_mock_basic.py
import pytest
# 安装:pip install pytest-mock
def test_mock_basic(mocker):
"""
基础mock测试
"""
# 创建mock对象
mock_func = mocker.Mock()
# 调用mock对象
mock_func(1, 2, 3)
# 验证调用
mock_func.assert_called_once_with(1, 2, 3)
assert mock_func.call_count == 1
def test_mock_return_value(mocker):
"""
mock返回值测试
"""
# 创建mock对象并设置返回值
mock_func = mocker.Mock(return_value=42)
# 调用mock对象
result = mock_func()
# 验证返回值
assert result == 42
def test_mock_side_effect(mocker):
"""
mock副作用测试
"""
# 创建mock对象并设置副作用
mock_func = mocker.Mock(side_effect=[1, 2, 3])
# 多次调用mock对象
assert mock_func() == 1
assert mock_func() == 2
assert mock_func() == 3
# 验证调用次数
assert mock_func.call_count == 3
def test_mock_exception(mocker):
"""
mock异常测试
"""
# 创建mock对象并设置异常
mock_func = mocker.Mock(side_effect=ValueError("测试异常"))
# 调用mock对象并捕获异常
with pytest.raises(ValueError, match="测试异常"):
mock_func()
8.2 mocker.patch方法详解
mocker.patch方法用于替换对象和函数。
mocker.patch基础使用
python
# test_mocker_patch.py
import pytest
class Database:
"""
数据库类
"""
def connect(self):
"""
连接数据库
"""
return "connected"
def query(self, sql):
"""
执行查询
"""
return "query_result"
def test_patch_method(mocker):
"""
patch方法测试
"""
# 创建数据库实例
db = Database()
# patch connect方法
mocker.patch.object(db, 'connect', return_value="mocked_connected")
# 调用patch后的方法
result = db.connect()
# 验证结果
assert result == "mocked_connected"
def test_patch_class_method(mocker):
"""
patch类方法测试
"""
# patch Database类的connect方法
mocker.patch.object(Database, 'connect', return_value="mocked_connected")
# 创建数据库实例并调用方法
db = Database()
result = db.connect()
# 验证结果
assert result == "mocked_connected"
def test_patch_function(mocker):
"""
patch函数测试
"""
# 定义测试函数
def get_data():
return "real_data"
# patch函数
mocker.patch('__main__.get_data', return_value="mocked_data")
# 调用patch后的函数
result = get_data()
# 验证结果
assert result == "mocked_data"
def test_patch_with_context(mocker):
"""
使用上下文管理器patch
"""
# 创建数据库实例
db = Database()
# 使用上下文管理器patch
with mocker.patch.object(db, 'connect', return_value="mocked_connected"):
result = db.connect()
assert result == "mocked_connected"
# 退出上下文后恢复原方法
result = db.connect()
assert result == "connected"
8.3 Mock对象高级特性
Mock对象提供了丰富的高级特性。
Mock对象高级特性示例
python
# test_mock_advanced.py
import pytest
def test_mock_call_args(mocker):
"""
测试mock调用参数
"""
# 创建mock对象
mock_func = mocker.Mock()
# 调用mock对象
mock_func(1, 2, key="value")
# 验证调用参数
assert mock_func.call_args == mocker.call(1, 2, key="value")
assert mock_func.call_args[0] == (1, 2)
assert mock_func.call_args[1] == {"key": "value"}
def test_mock_call_args_list(mocker):
"""
测试mock调用参数列表
"""
# 创建mock对象
mock_func = mocker.Mock()
# 多次调用mock对象
mock_func(1)
mock_func(2)
mock_func(3)
# 验证调用参数列表
assert len(mock_func.call_args_list) == 3
assert mock_func.call_args_list[0] == mocker.call(1)
assert mock_func.call_args_list[1] == mocker.call(2)
assert mock_func.call_args_list[2] == mocker.call(3)
def test_mock_method_calls(mocker):
"""
测试mock方法调用
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 调用mock对象的方法
mock_obj.method1(1)
mock_obj.method2(2)
mock_obj.method1(3)
# 验证方法调用
assert len(mock_obj.method_calls) == 3
assert mock_obj.method1.call_count == 2
assert mock_obj.method2.call_count == 1
def test_mock_assertions(mocker):
"""
测试mock断言
"""
# 创建mock对象
mock_func = mocker.Mock()
# 调用mock对象
mock_func(1, 2, 3)
mock_func(4, 5, 6)
# 各种断言方法
mock_func.assert_called()
mock_func.assert_any_call(1, 2, 3)
mock_func.assert_has_calls([
mocker.call(1, 2, 3),
mocker.call(4, 5, 6)
])
# 重置mock
mock_func.reset_mock()
# 重置后断言失败
with pytest.raises(AssertionError):
mock_func.assert_called()
8.4 MagicMock与Mock区别
MagicMock和Mock有一些重要的区别。
MagicMock与Mock对比
python
# test_magic_mock.py
import pytest
def test_magic_mock_vs_mock(mocker):
"""
MagicMock与Mock对比
"""
# 创建Mock对象
mock_obj = mocker.Mock()
# 创建MagicMock对象
magic_mock_obj = mocker.MagicMock()
# Mock对象不支持魔术方法
try:
result = mock_obj + 1
assert False, "Mock不应该支持魔术方法"
except TypeError:
pass
# MagicMock对象支持魔术方法
result = magic_mock_obj + 1
assert isinstance(result, mocker.MagicMock)
def test_magic_mock_attributes(mocker):
"""
测试MagicMock属性
"""
# 创建MagicMock对象
magic_mock = mocker.MagicMock()
# 访问不存在的属性会自动创建新的MagicMock
assert isinstance(magic_mock.some_attribute, mocker.MagicMock)
assert isinstance(magic_mock.another_attribute, mocker.MagicMock)
def test_magic_mock_spec(mocker):
"""
测试MagicMock规格
"""
# 定义类
class MyClass:
def method1(self):
pass
def method2(self):
pass
# 创建带规格的MagicMock
magic_mock = mocker.MagicMock(spec=MyClass)
# 可以访问规格中定义的方法
assert hasattr(magic_mock, 'method1')
assert hasattr(magic_mock, 'method2')
# 访问规格中未定义的属性会报错
with pytest.raises(AttributeError):
magic_mock.non_existent_method
8.5 patch装饰器使用
patch装饰器提供了便捷的patch方式。
patch装饰器示例
python
# test_patch_decorator.py
import pytest
from unittest.mock import patch
class ExternalService:
"""
外部服务类
"""
@staticmethod
def fetch_data():
"""
获取数据
"""
return "external_data"
def test_patch_decorator(patch):
"""
patch装饰器测试
"""
# 使用patch装饰器
@patch('__main__.ExternalService.fetch_data', return_value="mocked_data")
def test_function():
service = ExternalService()
return service.fetch_data()
# 调用测试函数
result = test_function()
# 验证结果
assert result == "mocked_data"
def test_patch_decorator_with_args(mocker):
"""
带参数的patch装饰器测试
"""
# 使用pytest-mock的patch
@mocker.patch('__main__.ExternalService.fetch_data', return_value="mocked_data")
def test_function(mock_fetch):
service = ExternalService()
result = service.fetch_data()
# 验证mock被调用
mock_fetch.assert_called_once()
return result
# 调用测试函数
result = test_function()
# 验证结果
assert result == "mocked_data"
def test_multiple_patches(mocker):
"""
多个patch测试
"""
# 定义多个函数
def func1():
return "func1"
def func2():
return "func2"
# 使用多个patch
with mocker.patch('__main__.func1', return_value="mocked_func1"):
with mocker.patch('__main__.func2', return_value="mocked_func2"):
assert func1() == "mocked_func1"
assert func2() == "mocked_func2"
8.6 Mock对象配置与验证
Mock对象支持丰富的配置和验证选项。
Mock对象配置与验证示例
python
# test_mock_config.py
import pytest
def test_mock_config_spec(mocker):
"""
测试mock规格配置
"""
# 定义类
class MyClass:
def method1(self):
pass
def method2(self):
pass
# 创建带规格的mock
mock_obj = mocker.Mock(spec=MyClass)
# 可以访问规格中定义的方法
mock_obj.method1()
mock_obj.method2()
# 访问规格中未定义的属性会报错
with pytest.raises(AttributeError):
mock_obj.non_existent_method()
def test_mock_config_spec_set(mocker):
"""
测试mock规格设置配置
"""
# 定义类
class MyClass:
attribute = "value"
def method(self):
pass
# 创建带规格设置的mock
mock_obj = mocker.Mock(spec_set=MyClass)
# 可以设置属性
mock_obj.attribute = "new_value"
assert mock_obj.attribute == "new_value"
# 访问规格中未定义的属性会报错
with pytest.raises(AttributeError):
mock_obj.non_existent_attribute
def test_mock_config_autospec(mocker):
"""
测试mock自动规格配置
"""
# 定义函数
def my_function(a, b, c=None):
return a + b + c
# 创建带自动规格的mock
mock_func = mocker.create_autospec(my_function)
# 正确调用
mock_func(1, 2)
mock_func(1, 2, 3)
# 错误调用会报错
with pytest.raises(TypeError):
mock_func(1) # 缺少参数
def test_mock_verification(mocker):
"""
测试mock验证
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 调用mock对象
mock_obj(1, 2, 3)
mock_obj(4, 5, 6)
# 验证调用
mock_obj.assert_called()
mock_obj.assert_called_once_with(1, 2, 3)
mock_obj.assert_any_call(4, 5, 6)
# 验证调用次数
assert mock_obj.call_count == 2
# 重置mock
mock_obj.reset_mock()
assert mock_obj.call_count == 0
8.7 Mock对象副作用管理
Mock对象支持复杂的副作用管理。
Mock对象副作用管理示例
python
# test_mock_side_effects.py
import pytest
def test_side_effect_sequence(mocker):
"""
测试副作用序列
"""
# 创建mock对象并设置副作用序列
mock_func = mocker.Mock(side_effect=[1, 2, 3, ValueError("错误")])
# 调用mock对象
assert mock_func() == 1
assert mock_func() == 2
assert mock_func() == 3
# 最后一次调用抛出异常
with pytest.raises(ValueError, match="错误"):
mock_func()
def test_side_effect_function(mocker):
"""
测试副作用函数
"""
# 定义副作用函数
def side_effect_func(*args, **kwargs):
return sum(args)
# 创建mock对象并设置副作用函数
mock_func = mocker.Mock(side_effect=side_effect_func)
# 调用mock对象
assert mock_func(1, 2) == 3
assert mock_func(3, 4) == 7
assert mock_func(5, 6) == 11
def test_side_effect_iterator(mocker):
"""
测试副作用迭代器
"""
# 创建mock对象并设置副作用迭代器
mock_func = mocker.Mock(side_effect=iter([1, 2, 3]))
# 调用mock对象
assert mock_func() == 1
assert mock_func() == 2
assert mock_func() == 3
# 迭代器耗尽后抛出StopIteration
with pytest.raises(StopIteration):
mock_func()
def test_side_effect_exception(mocker):
"""
测试副作用异常
"""
# 创建mock对象并设置异常副作用
mock_func = mocker.Mock(side_effect=ValueError("测试异常"))
# 调用mock对象并捕获异常
with pytest.raises(ValueError, match="测试异常"):
mock_func()
def test_side_effect_mixed(mocker):
"""
测试混合副作用
"""
# 创建mock对象并设置混合副作用
mock_func = mocker.Mock(side_effect=[1, 2, ValueError("错误"), 3])
# 调用mock对象
assert mock_func() == 1
assert mock_func() == 2
# 抛出异常
with pytest.raises(ValueError, match="错误"):
mock_func()
# 异常后继续返回值
assert mock_func() == 3
8.8 Mock对象属性访问控制
Mock对象支持精细的属性访问控制。
Mock对象属性访问控制示例
python
# test_mock_attributes.py
import pytest
def test_mock_attribute_access(mocker):
"""
测试mock属性访问
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 访问不存在的属性会自动创建新的mock
assert isinstance(mock_obj.some_attribute, mocker.Mock)
assert isinstance(mock_obj.another_attribute, mocker.Mock)
def test_mock_attribute_spec(mocker):
"""
测试mock属性规格
"""
# 定义类
class MyClass:
attribute1 = "value1"
attribute2 = "value2"
def method(self):
pass
# 创建带规格的mock
mock_obj = mocker.Mock(spec=MyClass)
# 可以访问规格中定义的属性
assert hasattr(mock_obj, 'attribute1')
assert hasattr(mock_obj, 'attribute2')
assert hasattr(mock_obj, 'method')
# 访问规格中未定义的属性会报错
with pytest.raises(AttributeError):
mock_obj.non_existent_attribute
def test_mock_attribute_assignment(mocker):
"""
测试mock属性赋值
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 设置属性
mock_obj.attribute1 = "value1"
mock_obj.attribute2 = "value2"
# 验证属性值
assert mock_obj.attribute1 == "value1"
assert mock_obj.attribute2 == "value2"
def test_mock_attribute_deletion(mocker):
"""
测试mock属性删除
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 设置属性
mock_obj.attribute = "value"
# 验证属性存在
assert hasattr(mock_obj, 'attribute')
# 删除属性
del mock_obj.attribute
# 验证属性不存在
assert not hasattr(mock_obj, 'attribute')
8.9 Mock对象方法调用记录
Mock对象可以详细记录方法调用。
Mock对象方法调用记录示例
python
# test_mock_method_calls.py
import pytest
def test_method_calls_recording(mocker):
"""
测试方法调用记录
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 调用方法
mock_obj.method1(1, 2)
mock_obj.method2(key="value")
mock_obj.method1(3, 4)
# 验证方法调用
assert len(mock_obj.method_calls) == 3
assert mock_obj.method1.call_count == 2
assert mock_obj.method2.call_count == 1
# 验证具体调用
mock_obj.method1.assert_has_calls([
mocker.call(1, 2),
mocker.call(3, 4)
])
def test_mock_calls_recording(mocker):
"""
测试mock调用记录
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 调用mock对象
mock_obj(1, 2, 3)
mock_obj(4, 5, 6)
mock_obj(7, 8, 9)
# 验证调用记录
assert len(mock_obj.mock_calls) == 3
assert mock_obj.mock_calls[0] == mocker.call(1, 2, 3)
assert mock_obj.mock_calls[1] == mocker.call(4, 5, 6)
assert mock_obj.mock_calls[2] == mocker.call(7, 8, 9)
def test_call_args_recording(mocker):
"""
测试调用参数记录
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 调用mock对象
mock_obj(1, 2, key="value")
# 验证调用参数
assert mock_obj.call_args == mocker.call(1, 2, key="value")
assert mock_obj.call_args[0] == (1, 2)
assert mock_obj.call_args[1] == {"key": "value"}
def test_call_args_list_recording(mocker):
"""
测试调用参数列表记录
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 多次调用mock对象
mock_obj(1, 2)
mock_obj(3, 4)
mock_obj(5, 6)
# 验证调用参数列表
assert len(mock_obj.call_args_list) == 3
assert mock_obj.call_args_list[0] == mocker.call(1, 2)
assert mock_obj.call_args_list[1] == mocker.call(3, 4)
assert mock_obj.call_args_list[2] == mocker.call(5, 6)
8.10 Mock最佳实践
使用Mock时需要遵循一些最佳实践。
Mock最佳实践示例
python
# test_mock_best_practices.py
import pytest
# 最佳实践1:使用明确的mock名称
def test_mock_with_name(mocker):
"""
使用明确的mock名称
"""
# 创建带名称的mock对象
mock_database = mocker.Mock(name='database_connection')
mock_api = mocker.Mock(name='api_client')
# 使用mock对象
mock_database.connect()
mock_api.get_data()
# 验证调用
mock_database.connect.assert_called_once()
mock_api.get_data.assert_called_once()
# 最佳实践2:使用spec限制mock接口
def test_mock_with_spec(mocker):
"""
使用spec限制mock接口
"""
# 定义类
class Database:
def connect(self):
pass
def disconnect(self):
pass
def query(self, sql):
pass
# 创建带spec的mock
mock_db = mocker.Mock(spec=Database)
# 只能调用spec中定义的方法
mock_db.connect()
mock_db.disconnect()
mock_db.query("SELECT * FROM users")
# 调用未定义的方法会报错
with pytest.raises(AttributeError):
mock_db.non_existent_method()
# 最佳实践3:使用autospec自动创建规格
def test_mock_with_autospec(mocker):
"""
使用autospec自动创建规格
"""
# 定义函数
def calculate_sum(a, b, c=None):
return a + b + (c or 0)
# 创建带autospec的mock
mock_calculate = mocker.create_autospec(calculate_sum)
# 正确调用
mock_calculate(1, 2)
mock_calculate(1, 2, 3)
# 错误调用会报错
with pytest.raises(TypeError):
mock_calculate(1) # 缺少参数
# 最佳实践4:使用side_effect处理复杂逻辑
def test_mock_with_side_effect(mocker):
"""
使用side_effect处理复杂逻辑
"""
# 定义副作用函数
def calculate_side_effect(*args, **kwargs):
if len(args) == 2:
return args[0] + args[1]
elif len(args) == 3:
return args[0] + args[1] + args[2]
else:
raise ValueError("参数数量错误")
# 创建带side_effect的mock
mock_calculate = mocker.Mock(side_effect=calculate_side_effect)
# 调用mock对象
assert mock_calculate(1, 2) == 3
assert mock_calculate(1, 2, 3) == 6
# 错误调用会抛出异常
with pytest.raises(ValueError, match="参数数量错误"):
mock_calculate(1)
# 最佳实践5:使用patch替换外部依赖
class ExternalAPI:
"""
外部API类
"""
@staticmethod
def fetch_user(user_id):
"""
获取用户信息
"""
return {"id": user_id, "name": "Real User"}
def test_with_patch(mocker):
"""
使用patch替换外部依赖
"""
# patch外部API
mocker.patch('__main__.ExternalAPI.fetch_user',
return_value={"id": 1, "name": "Mock User"})
# 调用patch后的方法
result = ExternalAPI.fetch_user(1)
# 验证结果
assert result["name"] == "Mock User"
# 最佳实践6:使用fixture管理mock
@pytest.fixture
def mock_database(mocker):
"""
数据库mock fixture
"""
mock_db = mocker.Mock()
mock_db.connect.return_value = "connected"
mock_db.query.return_value = [{"id": 1, "name": "User1"}]
return mock_db
def test_with_mock_fixture(mock_database):
"""
使用mock fixture
"""
# 使用mock数据库
assert mock_database.connect() == "connected"
assert mock_database.query("SELECT * FROM users") == [{"id": 1, "name": "User1"}]
# 最佳实践7:验证mock调用
def test_mock_verification(mocker):
"""
验证mock调用
"""
# 创建mock对象
mock_func = mocker.Mock()
# 调用mock对象
mock_func(1, 2, 3)
mock_func(4, 5, 6)
# 验证调用
mock_func.assert_called()
mock_func.assert_any_call(1, 2, 3)
mock_func.assert_has_calls([
mocker.call(1, 2, 3),
mocker.call(4, 5, 6)
])
# 最佳实践8:重置mock状态
def test_mock_reset(mocker):
"""
重置mock状态
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 调用mock对象
mock_obj.method1()
mock_obj.method2()
# 验证调用
assert mock_obj.method1.call_count == 1
assert mock_obj.method2.call_count == 1
# 重置mock
mock_obj.reset_mock()
# 验证重置后状态
assert mock_obj.method1.call_count == 0
assert mock_obj.method2.call_count == 0
# 最佳实践9:使用mock检查调用顺序
def test_mock_call_order(mocker):
"""
检查mock调用顺序
"""
# 创建mock对象
mock_obj = mocker.Mock()
# 调用方法
mock_obj.method1()
mock_obj.method2()
mock_obj.method3()
# 验证调用顺序
mock_obj.assert_has_calls([
mocker.call.method1(),
mocker.call.method2(),
mocker.call.method3()
])
# 最佳实践10:使用mock进行性能测试
def test_mock_performance(mocker):
"""
使用mock进行性能测试
"""
import time
# 创建mock对象
mock_func = mocker.Mock()
# 测试mock调用性能
start_time = time.time()
for _ in range(10000):
mock_func(1, 2, 3)
end_time = time.time()
# 验证性能
assert end_time - start_time < 1.0 # 10000次调用应该在1秒内完成
assert mock_func.call_count == 10000
9. pytest调试与测试工具
9.1 pytest调试基础
pytest提供了多种调试工具和方法。
pytest调试基础示例
python
# test_pytest_debug_basic.py
import pytest
def test_debug_basic():
"""
基础调试测试
"""
# 使用print语句调试
print("调试信息:开始测试")
# 测试逻辑
result = 1 + 1
print(f"调试信息:计算结果 = {result}")
# 断言
assert result == 2
def test_debug_with_assertion():
"""
使用assertion调试
"""
# 测试数据
data = [1, 2, 3, 4, 5]
# 调试信息
print(f"调试信息:数据列表 = {data}")
# 计算总和
total = sum(data)
print(f"调试信息:总和 = {total}")
# 断言
assert total == 15
def test_debug_with_exception():
"""
异常调试测试
"""
try:
# 可能抛出异常的代码
result = 10 / 0
except ZeroDivisionError as e:
# 捕获异常并打印调试信息
print(f"调试信息:捕获到异常 - {e}")
raise
9.2 使用pdb调试
pytest集成了Python调试器pdb。
pdb调试基础使用
python
# test_pdb_debug.py
import pytest
def test_pdb_breakpoint():
"""
使用breakpoint调试
"""
# 测试数据
numbers = [1, 2, 3, 4, 5]
# 设置断点(在pytest中会自动进入调试模式)
breakpoint()
# 计算总和
total = sum(numbers)
# 断言
assert total == 15
def test_pdb_import():
"""
使用pdb模块调试
"""
import pdb
# 测试数据
data = {"name": "Alice", "age": 30}
# 设置断点
pdb.set_trace()
# 处理数据
result = f"{data['name']} is {data['age']} years old"
# 断言
assert result == "Alice is 30 years old"
def test_pdb_with_exception():
"""
使用pdb调试异常
"""
import pdb
try:
# 可能抛出异常的代码
result = 10 / 0
except Exception as e:
# 在异常处设置断点
pdb.set_trace()
print(f"调试信息:异常类型 = {type(e)}")
print(f"调试信息:异常信息 = {e}")
raise
pdb命令参考
python
# test_pdb_commands.py
"""
pdb常用命令:
n (next): 执行下一行
s (step): 进入函数
c (continue): 继续执行
l (list): 显示代码
p (print): 打印变量
pp (pretty print): 美化打印变量
a (args): 显示当前函数参数
w (where): 显示调用栈
u (up): 向上移动调用栈
d (down): 向下移动调用栈
b (break): 设置断点
cl (clear): 清除断点
h (help): 显示帮助
q (quit): 退出调试
"""
def test_pdb_commands_example():
"""
pdb命令示例
"""
# 测试函数
def calculate_sum(a, b):
"""
计算两个数的和
"""
return a + b
# 测试数据
num1 = 10
num2 = 20
# 计算结果
result = calculate_sum(num1, num2)
# 断言
assert result == 30
9.3 pytest-sugar插件
pytest-sugar提供了更友好的测试输出。
pytest-sugar基础使用
python
# test_pytest_sugar.py
import pytest
# 安装:pip install pytest-sugar
def test_sugar_example_1():
"""
pytest-sugar示例测试1
"""
assert 1 + 1 == 2
def test_sugar_example_2():
"""
pytest-sugar示例测试2
"""
assert 2 * 3 == 6
def test_sugar_example_3():
"""
pytest-sugar示例测试3
"""
assert "hello".upper() == "HELLO"
def test_sugar_example_4():
"""
pytest-sugar示例测试4
"""
data = [1, 2, 3, 4, 5]
assert sum(data) == 15
def test_sugar_example_5():
"""
pytest-sugar示例测试5
"""
assert len("pytest") == 6
@pytest.mark.slow
def test_sugar_slow_test():
"""
慢速测试示例
"""
import time
time.sleep(0.1)
assert True
9.4 pytest-instafail插件
pytest-instafail在测试失败时立即显示错误。
pytest-instafail基础使用
python
# test_pytest_instafail.py
import pytest
# 安装:pip install pytest-instafail
def test_instafail_success():
"""
成功测试
"""
assert 1 + 1 == 2
def test_instafail_failure():
"""
失败测试
"""
assert 1 + 1 == 3 # 这会立即失败
def test_instafail_exception():
"""
异常测试
"""
raise ValueError("测试异常")
def test_instafail_after_failure():
"""
失败后的测试
"""
assert True # 这个测试可能不会执行
9.5 pytest-icdiff插件
pytest-icdiff提供了更好的差异显示。
pytest-icdiff基础使用
python
# test_pytest_icdiff.py
import pytest
# 安装:pip install pytest-icdiff
def test_icdiff_strings():
"""
字符串差异测试
"""
expected = "Hello, World!"
actual = "Hello, Python!"
assert actual == expected
def test_icdiff_lists():
"""
列表差异测试
"""
expected = [1, 2, 3, 4, 5]
actual = [1, 2, 3, 4, 6]
assert actual == expected
def test_icdiff_dicts():
"""
字典差异测试
"""
expected = {"name": "Alice", "age": 30, "city": "New York"}
actual = {"name": "Alice", "age": 31, "city": "New York"}
assert actual == expected
def test_icdiff_complex():
"""
复杂数据差异测试
"""
expected = {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
}
actual = {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Charlie"}
]
}
assert actual == expected
9.6 pytest-xdist与调试
pytest-xdist支持并行测试,但需要注意调试。
pytest-xdist调试示例
python
# test_pytest_xdist_debug.py
import pytest
# 安装:pip install pytest-xdist
def test_xdist_basic():
"""
基础xdist测试
"""
assert 1 + 1 == 2
def test_xdist_with_fixture(worker_id):
"""
使用worker_id fixture的xdist测试
"""
# worker_id显示当前工作进程ID
print(f"当前工作进程:{worker_id}")
assert True
@pytest.mark.parametrize("value", [1, 2, 3, 4, 5])
def test_xdist_parametrized(value):
"""
参数化xdist测试
"""
assert value > 0
def test_xdist_shared_state(worker_id):
"""
测试共享状态
"""
# 每个worker有独立的状态
import random
random.seed(42)
value = random.randint(1, 100)
print(f"Worker {worker_id} 生成的随机数:{value}")
assert 1 <= value <= 100
9.7 测试覆盖率工具集成
pytest-cov集成了代码覆盖率工具。
pytest-cov基础使用
python
# test_pytest_cov.py
import pytest
# 安装:pip install pytest-cov
def calculate_sum(a, b):
"""
计算两个数的和
"""
return a + b
def calculate_product(a, b):
"""
计算两个数的乘积
"""
return a * b
def test_calculate_sum():
"""
测试计算和
"""
assert calculate_sum(1, 2) == 3
assert calculate_sum(-1, 1) == 0
def test_calculate_product():
"""
测试计算乘积
"""
assert calculate_product(2, 3) == 6
assert calculate_product(-1, 5) == -5
def test_coverage_example():
"""
覆盖率示例测试
"""
# 这个测试会覆盖calculate_sum和calculate_product
assert calculate_sum(1, 2) == 3
assert calculate_product(2, 3) == 6
pytest-cov高级配置
python
# test_pytest_cov_advanced.py
import pytest
class Calculator:
"""
计算器类
"""
def __init__(self):
"""
初始化计算器
"""
self.history = []
def add(self, a, b):
"""
加法
"""
result = a + b
self.history.append(f"{a} + {b} = {result}")
return result
def subtract(self, a, b):
"""
减法
"""
result = a - b
self.history.append(f"{a} - {b} = {result}")
return result
def multiply(self, a, b):
"""
乘法
"""
result = a * b
self.history.append(f"{a} * {b} = {result}")
return result
def divide(self, a, b):
"""
除法
"""
if b == 0:
raise ValueError("除数不能为零")
result = a / b
self.history.append(f"{a} / {b} = {result}")
return result
def test_calculator_operations():
"""
测试计算器操作
"""
calc = Calculator()
# 测试加法
assert calc.add(1, 2) == 3
# 测试减法
assert calc.subtract(5, 3) == 2
# 测试乘法
assert calc.multiply(2, 3) == 6
# 测试除法
assert calc.divide(6, 2) == 3
def test_calculator_division_by_zero():
"""
测试除零异常
"""
calc = Calculator()
# 测试除零异常
with pytest.raises(ValueError, match="除数不能为零"):
calc.divide(1, 0)
def test_calculator_history():
"""
测试计算器历史记录
"""
calc = Calculator()
# 执行操作
calc.add(1, 2)
calc.subtract(5, 3)
# 验证历史记录
assert len(calc.history) == 2
assert calc.history[0] == "1 + 2 = 3"
assert calc.history[1] == "5 - 3 = 2"
9.8 性能分析工具
pytest-benchmark提供性能测试功能。
pytest-benchmark基础使用
python
# test_pytest_benchmark.py
import pytest
# 安装:pip install pytest-benchmark
def test_benchmark_simple(benchmark):
"""
简单性能测试
"""
def simple_function():
return sum(range(1000))
result = benchmark(simple_function)
assert result == 499500
def test_benchmark_list_comprehension(benchmark):
"""
列表推导式性能测试
"""
def list_comp():
return [x * 2 for x in range(1000)]
result = benchmark(list_comp)
assert len(result) == 1000
def test_benchmark_string_operations(benchmark):
"""
字符串操作性能测试
"""
def string_ops():
s = ""
for i in range(100):
s += str(i)
return s
result = benchmark(string_ops)
assert len(result) > 0
def test_benchmark_dict_operations(benchmark):
"""
字典操作性能测试
"""
def dict_ops():
d = {}
for i in range(1000):
d[f"key_{i}"] = i
return d
result = benchmark(dict_ops)
assert len(result) == 1000
def test_benchmark_comparison(benchmark):
"""
性能对比测试
"""
def method1():
return sum(range(1000))
def method2():
total = 0
for i in range(1000):
total += i
return total
result1 = benchmark(method1)
result2 = benchmark(method2)
assert result1 == result2
9.9 调试最佳实践
使用调试工具时需要遵循一些最佳实践。
调试最佳实践示例
python
# test_debug_best_practices.py
import pytest
# 最佳实践1:使用有意义的测试名称
def test_calculate_sum_with_positive_numbers():
"""
使用有意义的测试名称
"""
result = 1 + 2
assert result == 3
# 最佳实践2:使用描述性的断言消息
def test_with_descriptive_assertion():
"""
使用描述性的断言消息
"""
result = 1 + 2
assert result == 3, "1 + 2 应该等于 3"
# 最佳实践3:使用fixture设置测试数据
@pytest.fixture
def sample_data():
"""
示例数据fixture
"""
return [1, 2, 3, 4, 5]
def test_with_fixture(sample_data):
"""
使用fixture的测试
"""
total = sum(sample_data)
assert total == 15
# 最佳实践4:使用标记组织测试
@pytest.mark.unit
def test_unit_test():
"""
单元测试
"""
assert 1 + 1 == 2
@pytest.mark.integration
def test_integration_test():
"""
集成测试
"""
assert "hello".upper() == "HELLO"
# 最佳实践5:使用参数化测试
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(2, 3, 5),
(3, 4, 7),
])
def test_parametrized(a, b, expected):
"""
参数化测试
"""
assert a + b == expected
# 最佳实践6:使用上下文管理器处理资源
def test_with_context_manager():
"""
使用上下文管理器
"""
class Resource:
def __enter__(self):
print("资源已打开")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("资源已关闭")
return False
with Resource():
assert True
# 最佳实践7:使用自定义断言
def assert_positive_number(value):
"""
自定义断言:验证正数
"""
assert value > 0, f"值 {value} 应该是正数"
def test_custom_assertion():
"""
使用自定义断言
"""
assert_positive_number(10)
# 最佳实践8:使用日志记录
import logging
def test_with_logging(caplog):
"""
使用日志记录
"""
with caplog.at_level(logging.INFO):
logging.info("测试开始")
assert True
logging.info("测试完成")
assert "测试开始" in caplog.text
assert "测试完成" in caplog.text
# 最佳实践9:使用临时文件
def test_with_tmp_path(tmp_path):
"""
使用临时文件
"""
# 创建临时文件
file_path = tmp_path / "test.txt"
file_path.write_text("测试内容")
# 验证文件内容
assert file_path.read_text() == "测试内容"
# 最佳实践10:使用monkeypatch修改环境
def test_with_monkeypatch(monkeypatch):
"""
使用monkeypatch
"""
# 修改环境变量
monkeypatch.setenv("TEST_VAR", "test_value")
# 验证环境变量
import os
assert os.environ.get("TEST_VAR") == "test_value"
10. pytest生产最佳实践
10.1 项目结构组织
良好的项目结构是测试成功的基础。
推荐的项目结构
项目根目录
src
tests
docs
requirements.txt
setup.py
pytest.ini
.coveragerc
.gitignore
README.md
pyproject.toml
module1
module2
init.py
utils
models
services
init.py
main.py
config.py
init.py
api.py
database.py
init.py
helpers.py
decorators.py
init.py
user.py
product.py
init.py
user_service.py
product_service.py
unit
integration
conftest.py
init.py
fixtures
mocks
init.py
test_module1.py
test_module2.py
test_utils.py
init.py
test_api.py
test_database.py
test_integration.py
init.py
database_fixtures.py
api_fixtures.py
user_fixtures.py
init.py
mock_database.py
mock_api.py
api
architecture
testing
项目结构示例
python
# conftest.py - 项目级配置文件
import pytest
import os
import sys
# 添加项目根目录到Python路径
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
# 共享fixture
@pytest.fixture(scope="session")
def test_config():
"""
测试配置fixture
"""
return {
"database_url": "sqlite:///:memory:",
"api_base_url": "http://localhost:8000",
"timeout": 30
}
# 测试插件配置
# 注意:pytest_plugins变量可以用来加载插件,但推荐使用pytest.ini或pyproject.toml配置
# pytest_plugins = ["pytester"]
#
# 推荐的插件配置方式:
# 在pytest.ini中:
# [pytest]
# plugins = pytester
#
# 或者在pyproject.toml中:
# [tool.pytest.ini_options]
# plugins = ["pytester"]
# 配置pytest选项
def pytest_configure(config):
"""
配置pytest
"""
config.addinivalue_line("markers", "unit: 单元测试标记")
config.addinivalue_line("markers", "integration: 集成测试标记")
config.addinivalue_line("markers", "slow: 慢速测试标记")
config.addinivalue_line("markers", "smoke: 冒烟测试标记")
10.2 测试命名规范
清晰的命名规范有助于测试维护。
测试命名规范示例
python
# test_user_service.py - 用户服务测试
import pytest
class TestUserService:
"""
用户服务测试类
"""
def test_create_user_with_valid_data_should_succeed(self):
"""
使用有效数据创建用户应该成功
"""
# 测试逻辑
assert True
def test_create_user_with_duplicate_email_should_fail(self):
"""
使用重复邮箱创建用户应该失败
"""
# 测试逻辑
assert True
def test_update_user_profile_with_valid_data_should_succeed(self):
"""
使用有效数据更新用户资料应该成功
"""
# 测试逻辑
assert True
def test_delete_user_with_valid_id_should_succeed(self):
"""
使用有效ID删除用户应该成功
"""
# 测试逻辑
assert True
def test_get_user_by_id_with_valid_id_should_return_user(self):
"""
使用有效ID获取用户应该返回用户
"""
# 测试逻辑
assert True
10.3 测试数据管理
合理管理测试数据可以提高测试效率。
测试数据管理示例
python
# test_data_management.py
import pytest
import json
from pathlib import Path
# 使用fixture管理测试数据
@pytest.fixture
def test_user_data():
"""
测试用户数据fixture
"""
return {
"name": "Test User",
"email": "test@example.com",
"age": 30
}
@pytest.fixture
def test_product_data():
"""
测试产品数据fixture
"""
return {
"name": "Test Product",
"price": 99.99,
"quantity": 10
}
# 使用外部文件管理测试数据
@pytest.fixture
def load_test_data():
"""
从JSON文件加载测试数据
"""
data_file = Path(__file__).parent / "test_data.json"
if data_file.exists():
with open(data_file, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
# 使用参数化测试数据
@pytest.mark.parametrize("input_data,expected", [
({"name": "Alice", "age": 25}, True),
({"name": "Bob", "age": 30}, True),
({"name": "", "age": 20}, False),
({"name": "Charlie", "age": -1}, False),
])
def test_user_validation(input_data, expected):
"""
测试用户验证
"""
# 测试逻辑
assert expected
# 使用工厂模式生成测试数据
class TestDataFactory:
"""
测试数据工厂
"""
@staticmethod
def create_user(**kwargs):
"""
创建用户数据
"""
default_data = {
"name": "Test User",
"email": "test@example.com",
"age": 30
}
default_data.update(kwargs)
return default_data
@staticmethod
def create_product(**kwargs):
"""
创建产品数据
"""
default_data = {
"name": "Test Product",
"price": 99.99,
"quantity": 10
}
default_data.update(kwargs)
return default_data
@pytest.fixture
def user_factory():
"""
用户数据工厂fixture
"""
return TestDataFactory.create_user
def test_with_factory(user_factory):
"""
使用工厂创建测试数据
"""
user = user_factory(name="Alice", age=25)
assert user["name"] == "Alice"
assert user["age"] == 25
10.4 测试隔离与独立性
测试应该相互独立,不依赖于执行顺序。
测试隔离示例
python
# test_isolation.py
import pytest
# 每个测试使用独立的fixture
@pytest.fixture
def isolated_database():
"""
隔离的数据库fixture
"""
# 创建临时数据库
db = {}
yield db
# 清理数据库
db.clear()
def test_user_creation(isolated_database):
"""
测试用户创建
"""
# 使用隔离的数据库
isolated_database["user1"] = {"name": "Alice"}
assert "user1" in isolated_database
def test_user_deletion(isolated_database):
"""
测试用户删除
"""
# 使用隔离的数据库
isolated_database["user2"] = {"name": "Bob"}
del isolated_database["user2"]
assert "user2" not in isolated_database
# 使用autouse fixture确保测试隔离
@pytest.fixture(autouse=True)
def reset_test_state():
"""
自动重置测试状态
"""
# 测试前设置
yield
# 测试后清理
# 这里可以添加清理逻辑
# 使用mock确保测试隔离
def test_with_mock(mocker):
"""
使用mock确保测试隔离
"""
# mock外部依赖
mock_api = mocker.patch('module.external_api')
mock_api.return_value = {"status": "success"}
# 测试逻辑
result = mock_api()
assert result["status"] == "success"
10.5 测试覆盖率管理
合理的覆盖率目标有助于保证代码质量。
测试覆盖率管理示例
python
# test_coverage_management.py
import pytest
# 配置覆盖率目标
# 在.coveragerc文件中配置:
# [run]
# source = src
# omit =
# */tests/*
# */venv/*
#
# [report]
# exclude_lines =
# pragma: no cover
# def __repr__
# raise AssertionError
# raise NotImplementedError
# if __name__ == .__main__.:
#
# [html]
# directory = htmlcov
def test_critical_path_coverage():
"""
测试关键路径覆盖率
"""
# 测试关键业务逻辑
assert True
def test_edge_case_coverage():
"""
测试边界情况覆盖率
"""
# 测试边界条件
assert True
def test_error_handling_coverage():
"""
测试错误处理覆盖率
"""
# 测试错误处理逻辑
assert True
# 使用标记管理覆盖率
@pytest.mark.coverage_critical
def test_critical_function():
"""
关键函数测试
"""
assert True
@pytest.mark.coverage_important
def test_important_function():
"""
重要函数测试
"""
assert True
10.6 持续集成配置
将pytest集成到CI/CD流程中。
GitHub Actions配置示例
yaml
# .github/workflows/test.yml
name: Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, 3.10, 3.11]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov pytest-xdist pytest-mock
pip install -r requirements.txt
- name: Run tests with coverage
run: |
pytest --cov=src --cov-report=xml --cov-report=html --cov-report=term
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
GitLab CI配置示例
yaml
# .gitlab-ci.yml
stages:
- test
- coverage
test:
stage: test
image: python:3.10
script:
- pip install pytest pytest-cov pytest-xdist pytest-mock
- pip install -r requirements.txt
- pytest --cov=src --cov-report=xml --cov-report=term
artifacts:
paths:
- coverage.xml
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
coverage:
stage: coverage
image: python:3.10
script:
- pip install coverage
- coverage report
dependencies:
- test
only:
- main
- develop
10.7 测试性能优化
优化测试执行速度以提高开发效率。
测试性能优化示例
python
# test_performance_optimization.py
import pytest
import time
# 使用scope减少fixture创建次数
@pytest.fixture(scope="session")
def session_resource():
"""
会话级资源fixture
"""
# 只创建一次
resource = {"data": "session_data"}
return resource
@pytest.fixture(scope="module")
def module_resource():
"""
模块级资源fixture
"""
# 每个模块创建一次
resource = {"data": "module_data"}
return resource
# 使用并行测试
@pytest.mark.parametrize("value", range(100))
def test_parallel_execution(value):
"""
并行执行测试
"""
assert value >= 0
# 使用标记跳过慢速测试
@pytest.mark.slow
def test_slow_operation():
"""
慢速操作测试
"""
time.sleep(1)
assert True
# 使用缓存优化测试
@pytest.fixture
def cached_result(cache):
"""
缓存结果fixture
"""
key = "test_key"
if key in cache:
return cache[key]
# 计算结果
result = sum(range(1000))
cache[key] = result
return result
def test_with_cache(cached_result):
"""
使用缓存的测试
"""
assert cached_result == 499500
# 使用mock优化测试
def test_with_mock_optimization(mocker):
"""
使用mock优化测试
"""
# mock慢速操作
mock_slow_function = mocker.patch('module.slow_function')
mock_slow_function.return_value = "fast_result"
# 测试逻辑
result = mock_slow_function()
assert result == "fast_result"
10.8 测试文档与注释
良好的文档有助于团队协作。
测试文档示例
python
# test_documentation.py
import pytest
class TestUserService:
"""
用户服务测试类
测试用户服务的各种功能,包括:
- 用户创建
- 用户更新
- 用户删除
- 用户查询
测试策略:
- 使用mock隔离外部依赖
- 使用fixture管理测试数据
- 使用参数化测试覆盖多种场景
"""
def test_create_user_success(self, user_data):
"""
测试成功创建用户
测试场景:
- 使用有效的用户数据
- 验证用户创建成功
- 验证返回的用户信息
预期结果:
- 用户创建成功
- 返回的用户信息包含所有必要字段
"""
# Arrange
expected_name = user_data["name"]
# Act
# 这里调用实际的用户创建逻辑
result = {"name": expected_name, "id": 1}
# Assert
assert result["name"] == expected_name
assert result["id"] > 0
@pytest.mark.parametrize("invalid_data,error_message", [
({"name": ""}, "用户名不能为空"),
({"email": "invalid"}, "邮箱格式不正确"),
({"age": -1}, "年龄必须大于0"),
])
def test_create_user_validation(self, invalid_data, error_message):
"""
测试用户创建验证
测试场景:
- 使用无效的用户数据
- 验证验证逻辑正确
参数说明:
- invalid_data: 无效的用户数据
- error_message: 期望的错误消息
预期结果:
- 抛出验证异常
- 异常消息符合预期
"""
# Arrange & Act & Assert
with pytest.raises(ValueError, match=error_message):
# 这里调用实际的用户创建逻辑
pass
# 测试模块文档字符串
"""
用户服务测试模块
本模块包含用户服务的所有测试用例。
测试组织:
- TestUserService: 用户服务测试类
- test_create_user_*: 用户创建相关测试
- test_update_user_*: 用户更新相关测试
- test_delete_user_*: 用户删除相关测试
- test_query_user_*: 用户查询相关测试
依赖项:
- pytest: 测试框架
- pytest-mock: mock工具
- faker: 测试数据生成
运行方式:
- 运行所有测试: pytest test_user_service.py
- 运行特定类: pytest test_user_service.py::TestUserService
- 运行特定测试: pytest test_user_service.py::TestUserService::test_create_user_success
- 运行标记测试: pytest -m "unit" test_user_service.py
"""
10.9 测试维护与重构
定期维护和重构测试代码。
测试维护示例
python
# test_maintenance.py
import pytest
# 使用基类减少重复代码
class BaseTest:
"""
测试基类
"""
@pytest.fixture(autouse=True)
def setup_base(self):
"""
基础设置
"""
# 测试前设置
yield
# 测试后清理
class TestUserService(BaseTest):
"""
用户服务测试类
"""
def test_user_operation(self):
"""
用户操作测试
"""
assert True
# 使用辅助函数简化测试
def assert_valid_user(user):
"""
验证用户数据有效
参数:
user: 用户数据字典
断言:
- 用户名不为空
- 邮箱格式正确
- 年龄在合理范围内
"""
assert user["name"], "用户名不能为空"
assert "@" in user["email"], "邮箱格式不正确"
assert 0 < user["age"] < 150, "年龄必须在0到150之间"
def test_user_validation():
"""
测试用户验证
"""
user = {"name": "Alice", "email": "alice@example.com", "age": 30}
assert_valid_user(user)
# 使用自定义断言
class CustomAssertions:
"""
自定义断言类
"""
@staticmethod
def assert_success_response(response):
"""
断言成功响应
"""
assert response["status"] == "success"
assert "data" in response
@staticmethod
def assert_error_response(response, error_code):
"""
断言错误响应
"""
assert response["status"] == "error"
assert response["error_code"] == error_code
def test_api_response():
"""
测试API响应
"""
response = {"status": "success", "data": {"id": 1}}
CustomAssertions.assert_success_response(response)
10.10 测试最佳实践总结
综合应用各种最佳实践。
综合最佳实践示例
python
# test_best_practices_comprehensive.py
import pytest
from typing import Dict, Any
# 最佳实践1:使用清晰的测试结构
class TestOrderService:
"""
订单服务测试类
测试订单服务的核心功能,包括订单创建、更新、取消等。
"""
@pytest.fixture
def order_data(self) -> Dict[str, Any]:
"""
订单数据fixture
返回标准的订单测试数据。
"""
return {
"user_id": 1,
"product_id": 100,
"quantity": 2,
"price": 99.99
}
@pytest.mark.unit
def test_create_order_success(self, order_data, mocker):
"""
测试成功创建订单
Arrange:
- 准备订单数据
- mock外部依赖
Act:
- 调用订单创建方法
Assert:
- 验证订单创建成功
- 验证返回数据正确
"""
# Arrange
mock_db = mocker.patch('module.database')
mock_db.insert.return_value = {"id": 1, **order_data}
# Act
result = {"id": 1, **order_data}
# Assert
assert result["id"] > 0
assert result["user_id"] == order_data["user_id"]
mock_db.insert.assert_called_once()
@pytest.mark.integration
@pytest.mark.slow
def test_order_processing_workflow(self, order_data):
"""
测试订单处理工作流
测试完整的订单处理流程,包括:
1. 订单创建
2. 库存检查
3. 支付处理
4. 订单确认
"""
# 测试完整工作流
assert True
# 最佳实践2:使用参数化测试覆盖多种场景
@pytest.mark.parametrize("status,expected_result", [
("pending", "订单待处理"),
("processing", "订单处理中"),
("shipped", "订单已发货"),
("delivered", "订单已送达"),
("cancelled", "订单已取消"),
])
def test_order_status_translation(status, expected_result):
"""
测试订单状态翻译
参数:
status: 订单状态
expected_result: 期望的翻译结果
"""
# 测试状态翻译逻辑
assert True
# 最佳实践3:使用标记组织测试
@pytest.mark.unit
def test_unit_example():
"""
单元测试示例
快速执行的测试,不依赖外部系统。
"""
assert True
@pytest.mark.integration
def test_integration_example():
"""
集成测试示例
测试多个组件的集成,可能较慢。
"""
assert True
@pytest.mark.smoke
def test_smoke_example():
"""
冒烟测试示例
验证核心功能正常工作。
"""
assert True
# 最佳实践4:使用fixture管理资源
@pytest.fixture(scope="session")
def database_connection():
"""
数据库连接fixture
会话级别,整个测试会话只创建一次。
"""
# 创建连接
connection = {"connected": True}
yield connection
# 清理连接
connection.clear()
@pytest.fixture
def test_data():
"""
测试数据fixture
函数级别,每个测试函数都创建新的实例。
"""
return {"test": "data"}
# 最佳实践5:使用mock隔离依赖
def test_with_mock_isolation(mocker):
"""
使用mock隔离外部依赖
确保测试不依赖于外部系统。
"""
# mock外部API
mock_api = mocker.patch('module.external_api')
mock_api.return_value = {"status": "success"}
# 测试逻辑
result = mock_api()
assert result["status"] == "success"
# 最佳实践6:使用异常测试
def test_exception_handling(mocker):
"""
测试异常处理
验证代码正确处理异常情况。
"""
# mock抛出异常
mock_function = mocker.patch('module.function')
mock_function.side_effect = ValueError("测试异常")
# 测试异常处理
with pytest.raises(ValueError, match="测试异常"):
mock_function()
# 最佳实践7:使用性能测试
def test_performance_benchmark(benchmark):
"""
性能基准测试
确保代码性能符合预期。
"""
def operation():
return sum(range(1000))
result = benchmark(operation)
assert result == 499500
# 最佳实践8:使用覆盖率测试
@pytest.mark.coverage_critical
def test_critical_path_coverage():
"""
关键路径覆盖率测试
确保关键业务逻辑有足够的测试覆盖。
"""
# 测试关键路径
assert True
# 最佳实践9:使用文档字符串
def test_with_documentation():
"""
测试文档示例
这个测试展示了如何在测试中添加文档。
测试目的:
验证某个功能正常工作
测试步骤:
1. 准备测试数据
2. 执行被测试功能
3. 验证结果
预期结果:
功能正常工作,结果符合预期
"""
# 测试逻辑
assert True
# 最佳实践10:使用自定义断言
def assert_valid_order(order: Dict[str, Any]) -> None:
"""
自定义断言:验证订单数据有效
参数:
order: 订单数据字典
断言:
- 订单ID存在且大于0
- 用户ID存在且大于0
- 产品ID存在且大于0
- 数量大于0
- 价格大于0
"""
assert order["id"] > 0, "订单ID必须大于0"
assert order["user_id"] > 0, "用户ID必须大于0"
assert order["product_id"] > 0, "产品ID必须大于0"
assert order["quantity"] > 0, "数量必须大于0"
assert order["price"] > 0, "价格必须大于0"
def test_order_validation():
"""
测试订单验证
使用自定义断言验证订单数据。
"""
order = {
"id": 1,
"user_id": 100,
"product_id": 200,
"quantity": 2,
"price": 99.99
}
assert_valid_order(order)