Mock 和测试替身
Mock 隔离外部依赖(数据库、API、文件系统),让测试快速、可靠、可控。
为什么需要 Mock
问题:测试依赖外部服务:
python
import requests
def get_weather(city):
response = requests.get(f"https://api.weather.com/v1/{city}")
return response.json()
# 测试问题:
# - 依赖网络
# - API 可能宕机
# - 响应不可控
# - 测试慢解决:用 Mock 替代真实 API。
unittest.mock 基础
Mock 对象
python
from unittest.mock import Mock
# 创建 Mock
mock_api = Mock()
# 设置返回值
mock_api.get_data.return_value = {"temperature": 25}
# 调用
result = mock_api.get_data()
print(result) # {"temperature": 25}
# 验证调用
mock_api.get_data.assert_called_once()patch 装饰器
替换模块中的对象:
python
from unittest.mock import patch
import requests
def get_weather(city):
response = requests.get(f"https://api.weather.com/v1/{city}")
return response.json()
@patch('__main__.requests.get')
def test_get_weather(mock_get):
# 设置 Mock 响应
mock_get.return_value.json.return_value = {"temp": 25}
result = get_weather("Beijing")
assert result["temp"] == 25
mock_get.assert_called_once_with("https://api.weather.com/v1/Beijing")MagicMock
支持魔术方法的 Mock:
python
from unittest.mock import MagicMock
mock_list = MagicMock()
mock_list.__len__.return_value = 5
mock_list.__getitem__.return_value = 42
print(len(mock_list)) # 5
print(mock_list[0]) # 42何时使用 Mock
✅ 应该 Mock
- 外部 API:HTTP 请求、第三方服务
- 数据库:慢、需要设置
- 文件系统:避免实际 I/O
- 随机性:时间、随机数
- 慢操作:sleep、重计算
❌ 不应该 Mock
- 被测试的代码本身
- 简单的数据结构(list、dict)
- 纯函数(无副作用)
实战案例:模拟文件系统
python
# file_processor.py
def read_config(filename):
with open(filename) as f:
return f.read()
def process_file(filename):
content = read_config(filename)
return content.upper()
# test_file_processor.py
from unittest.mock import patch, mock_open
def test_process_file():
mock_data = "hello world"
with patch("builtins.open", mock_open(read_data=mock_data)):
result = process_file("config.txt")
assert result == "HELLO WORLD"实战案例:模拟网络请求
python
# weather_service.py
import requests
class WeatherService:
def get_temperature(self, city):
response = requests.get(f"https://api.weather.com/{city}")
response.raise_for_status()
data = response.json()
return data["temperature"]
# test_weather_service.py
import pytest
from unittest.mock import patch, Mock
import requests
@patch('weather_service.requests.get')
def test_get_temperature_success(mock_get):
# 模拟成功响应
mock_response = Mock()
mock_response.json.return_value = {"temperature": 25}
mock_get.return_value = mock_response
service = WeatherService()
temp = service.get_temperature("Beijing")
assert temp == 25
@patch('weather_service.requests.get')
def test_get_temperature_error(mock_get):
# 模拟网络错误
mock_get.side_effect = requests.ConnectionError("Network error")
service = WeatherService()
with pytest.raises(requests.ConnectionError):
service.get_temperature("Beijing")pytest-mock
pytest 的 Mock 插件:
bash
pip install pytest-mock使用 mocker fixture:
python
def get_user_name(user_id):
import requests
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()["name"]
def test_get_user_name(mocker):
# 使用 mocker.patch 替代 @patch
mock_get = mocker.patch('requests.get')
mock_get.return_value.json.return_value = {"name": "Alice"}
result = get_user_name(123)
assert result == "Alice"优点:
- 自动清理
- 与 pytest fixtures 集成
- 更简洁的语法
Mock 的常用方法
return_value
python
mock = Mock()
mock.method.return_value = 42
print(mock.method()) # 42side_effect
python
# 抛出异常
mock.method.side_effect = ValueError("Error")
# 多次调用不同返回值
mock.method.side_effect = [1, 2, 3]
print(mock.method()) # 1
print(mock.method()) # 2assert 方法
python
mock = Mock()
mock.method(1, 2, key="value")
mock.method.assert_called()
mock.method.assert_called_once()
mock.method.assert_called_with(1, 2, key="value")
mock.method.assert_called_once_with(1, 2, key="value")实战案例:模拟数据库
python
# user_repository.py
class UserRepository:
def __init__(self, db):
self.db = db
def get_user(self, user_id):
return self.db.query(f"SELECT * FROM users WHERE id={user_id}")
def create_user(self, name, email):
return self.db.execute(f"INSERT INTO users (name, email) VALUES ('{name}', '{email}')")
# test_user_repository.py
from unittest.mock import Mock
def test_get_user():
# 模拟数据库
mock_db = Mock()
mock_db.query.return_value = {"id": 1, "name": "Alice"}
repo = UserRepository(mock_db)
user = repo.get_user(1)
assert user["name"] == "Alice"
mock_db.query.assert_called_once_with("SELECT * FROM users WHERE id=1")
def test_create_user():
mock_db = Mock()
mock_db.execute.return_value = True
repo = UserRepository(mock_db)
result = repo.create_user("Bob", "bob@example.com")
assert result is True
mock_db.execute.assert_called_once()小结
- Mock 隔离外部依赖,提高测试速度和可靠性
- unittest.mock 是标准库
- pytest-mock 更简洁,与 pytest 集成
- patch 替换模块对象
- side_effect 模拟异常和多次调用
- Mock 外部服务,不 Mock 业务逻辑
下一节学习 调试和性能分析。
练习:答案