Управление состоянием мок-объектов между тестами — важная задача, особенно при интеграционном или сквозном тестировании, когда нужно эмулировать поведение внешних сервисов или компонентов. Существует несколько подходов, каждый со своими преимуществами и недостатками:
1. Использование фикстур с областью видимости (scope) "session" или "module" в pytest:
Фикстуры с областью видимости "session" создаются один раз для всей тестовой сессии, а фикстуры с областью видимости "module" создаются один раз для всего модуля. Это позволяет сохранять состояние мок-объекта между тестами в пределах сессии или модуля. В pytest это делается указанием параметра scope
при определении фикстуры. Например:
import pytest
from unittest.mock import Mock
@pytest.fixture(scope="module") # Или "session"
def my_mock():
mock = Mock()
mock.some_attribute = 0 # Начальное состояние
return mock
def test_one(my_mock):
my_mock.some_attribute += 1
assert my_mock.some_attribute == 1
def test_two(my_mock):
assert my_mock.some_attribute == 1 # Состояние сохранено после test_one
my_mock.some_attribute += 2
assert my_mock.some_attribute == 3
Преимущества: Удобно для интеграционных тестов, где нужно эмулировать сохранение данных между операциями. Легко настраивается в pytest.
Недостатки: Может привести к запутанным зависимостям между тестами, если состояние мок-объекта изменяется непредсказуемо. Труднее понять порядок выполнения тестов и влияние каждого теста на состояние мока. Важно тщательно продумывать, когда использовать session или module scope.
2. Использование паттернов проектирования, таких как Singleton или Factory, для мок-объектов:
Если вам нужна гарантия, что все тесты используют один и тот же экземпляр мок-объекта, можно использовать Singleton. Factory позволит создавать экземпляры моков с определенными начальными состояниями.
class MockSingleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = Mock()
cls._instance.state = 0
return cls._instance
def test_one():
mock = MockSingleton()
mock.state += 1
assert mock.state == 1
def test_two():
mock = MockSingleton()
assert mock.state == 1 # Состояние сохранено
Преимущества: Более контролируемое управление состоянием. Централизованное место для инициализации и изменения состояния мок-объекта.
Недостатки: Требует дополнительного кода для реализации паттернов. Может быть сложнее для понимания, чем простые фикстуры. Singleton может нарушать принципы SOLID, если используется злоупотребление.
3. Сброс состояния мок-объекта в каждой функции setUp/tearDown (или фикстурах с scope "function"):
Этот подход, наоборот, фокусируется на изоляции тестов. После каждого теста состояние мок-объекта сбрасывается в начальное состояние. Это помогает избежать нежелательных зависимостей между тестами.
import pytest
from unittest.mock import Mock
@pytest.fixture
def my_mock():
mock = Mock()
mock.some_attribute = 0 # Начальное состояние
yield mock
mock.some_attribute = 0 # Сброс состояния после теста
def test_one(my_mock):
my_mock.some_attribute += 1
assert my_mock.some_attribute == 1
def test_two(my_mock):
assert my_mock.some_attribute == 0 # Состояние сброшено
my_mock.some_attribute += 2
assert my_mock.some_attribute == 2
Преимущества: Уменьшает зависимость между тестами, упрощает отладку. Делает тесты более предсказуемыми и легкими для понимания.
Недостатки: Может быть менее удобным для интеграционных тестов, где нужно эмулировать сохранение состояния. Требует дополнительного кода для сброса состояния.
4. Использование отдельных мок-объектов для каждого теста:
Самый простой и, зачастую, самый рекомендуемый подход - создавать новый мок-объект для каждого теста. Это обеспечивает полную изоляцию тестов и исключает возможность нежелательных взаимодействий.
from unittest.mock import Mock
def test_one():
mock = Mock()
mock.some_attribute = 0
mock.some_attribute += 1
assert mock.some_attribute == 1
def test_two():
mock = Mock() # Новый мок-объект
mock.some_attribute = 0
mock.some_attribute += 2
assert mock.some_attribute == 2
Преимущества: Полная изоляция, простота, минимальный риск влияния одного теста на другой.
Недостатки: Может быть неэффективным, если создание мок-объекта требует значительных ресурсов. Может потребовать дублирования кода для настройки мок-объекта.
Выбор подхода зависит от:
Важно: Независимо от выбранного подхода, важно тщательно документировать состояние и поведение мок-объекта, чтобы тесты оставались понятными и поддерживаемыми. Необходимо избегать слишком сложных моков, которые имитируют слишком много поведения реального объекта. В таких случаях может быть полезнее использовать тестовые двойники (stubs) или фейковые объекты.