Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Типы тестов с Mock объектами
Тесты, использующие mock объекты, имеют специальную терминологию и классификацию в зависимости от уровня и подхода.
Unit Tests (Модульные тесты)
Основной тип тестов с mock. Тестируют одну функцию/метод в изоляции:
import unittest
from unittest.mock import Mock, patch
class PaymentService:
def __init__(self, gateway):
self.gateway = gateway
def process_payment(self, amount):
return self.gateway.charge(amount)
class TestPaymentService(unittest.TestCase):
def test_process_payment(self):
# Mock объект вместо реального gateway
mock_gateway = Mock()
mock_gateway.charge.return_value = True
service = PaymentService(mock_gateway)
result = service.process_payment(100)
self.assertTrue(result)
mock_gateway.charge.assert_called_once_with(100)
Test Doubles — общее название для всех подменяемых объектов
Это группа тестовых техник:
- Mock — проверяет взаимодействие (был ли вызван метод)
- Stub — возвращает заранее подготовленные данные
- Fake — рабочая реализация для тестирования
- Spy — отслеживает вызовы реального объекта
- Dummy — пустой объект для заполнения параметров
# Stub — возвращает фиксированное значение
stub_user = Mock()
stub_user.get_age.return_value = 25
# Dummy — просто заполняет параметр
dummy_logger = Mock()
service = MyService(dummy_logger) # logger не используется
# Spy — отслеживает реальный объект
with patch('module.real_function') as spy:
spy.side_effect = lambda x: x * 2
result = spy(5)
spy.assert_called_once_with(5)
Isolation Tests (Тесты изоляции)
Тесты, полностью изолирующие код от зависимостей:
from unittest.mock import patch
class UserRepository:
def get_user(self, user_id):
# Реальный запрос в БД
pass
class UserService:
def __init__(self, repo):
self.repo = repo
def get_user_age(self, user_id):
user = self.repo.get_user(user_id)
return user['age']
def test_user_service():
# Изолируем от БД
mock_repo = Mock()
mock_repo.get_user.return_value = {'name': 'Alice', 'age': 30}
service = UserService(mock_repo)
age = service.get_user_age(1)
assert age == 30
mock_repo.get_user.assert_called_once_with(1)
State-based Testing vs Behavior-based Testing
State-based (Mock as Stub):
- Проверяет изменение состояния после вызова
- Фокус на результате
def test_increment_state():
counter = Counter()
counter.increment()
assert counter.value == 1 # Проверяем состояние
Behavior-based (Mock assertions):
- Проверяет, были ли вызваны методы и с какими аргументами
- Фокус на взаимодействии
def test_increment_behavior():
mock_observer = Mock()
counter = Counter(mock_observer)
counter.increment()
# Проверяем вызов
mock_observer.on_increment.assert_called_once()
Integration Tests с частичным mocking
Тесты, которые тестируют взаимодействие нескольких компонентов:
class TestOrderService(unittest.TestCase):
@patch('services.payment_gateway')
def test_create_order(self, mock_payment):
# Мокируем только payment gateway
# Остальное работает реально
mock_payment.charge.return_value = True
order_service = OrderService()
order = order_service.create_order(items=[...])
assert order.status == 'paid'
mock_payment.charge.assert_called_once()
Parameterized Tests с Mock
Тесты с разными параметрами для одной функции:
import pytest
from unittest.mock import Mock
class TestValidator:
@pytest.mark.parametrize('input,expected', [
('abc', True),
('123', False),
('', False),
])
def test_validate_string(self, input, expected):
mock_logger = Mock()
validator = Validator(mock_logger)
result = validator.is_valid(input)
assert result == expected
Snapshot Tests с Mock
Тесты, проверяющие неизменяемость поведения:
import pytest
from unittest.mock import Mock
def test_api_response_snapshot(snapshot):
mock_api = Mock()
mock_api.get_users.return_value = [
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': 'Bob'},
]
# Проверяем, что ответ соответствует сохранённому
snapshot.assert_match(mock_api.get_users())
Contract Tests
Тесты, проверяющие контракт между компонентами:
from unittest.mock import Mock, call
def test_payment_contract():
# Контракт: payment gateway должен быть вызван определённым образом
mock_gateway = Mock(spec=['charge', 'refund'])
service = PaymentService(mock_gateway)
service.process_payment(100)
service.refund_payment(100)
# Проверяем контракт
expected_calls = [call.charge(100), call.refund(100)]
assert mock_gateway.method_calls == expected_calls
Mutation Tests
Тесты, проверяющие качество других тестов (мутируют код и смотрят, упадут ли тесты):
# Используют инструменты типа mutmut, cosmic-ray
# Проверяют, что тесты действительно ловят баги
Golden Master Tests
Тесты, проверяющие, что вывод соответствует эталонному:
def test_report_generation():
mock_data = Mock()
mock_data.get_records.return_value = [...]
report = generate_report(mock_data)
# Сравниваем с эталонным отчётом
assert report == golden_master_report
Практические рекомендации
Когда использовать Mock:
- Тестирование внешних зависимостей (API, БД, файловая система)
- Изолирование бизнес-логики для быстрых unit тестов
- Проверка взаимодействия между компонентами
Когда избегать Mock:
- Не мокируй то, что быстро работает (обычные классы)
- Не создавай сложные моки — это признак плохого дизайна
- Используй реальные объекты для интеграционных тестов
Best Practices:
- Мокируй только необходимые зависимости
- Используй Mock для проверки вызовов, Stub для подготовки данных
- Не переусложняй моки — они должны быть понятны
- Комбинируй unit тесты с mock и интеграционные тесты с реальными объектами