unittest.mock
.
MagicMock
: Подходит для большинства случаев. Можно настроить возвращаемые значения и побочные эффекты (side_effect
) для разных вызовов методов.Mock
: Более простой, но менее мощный, чем MagicMock
.patch
: Для замены реальных объектов моками в тестах. Используйте его для подмены объектов с большим количеством методов.Mock
или MagicMock
, и переопределите нужные методы, чтобы они возвращали желаемые значения или выполняли определенные действия. Это дает максимальный контроль.MagicMock
):
from unittest.mock import MagicMock
# Создаем мок-объект
mock_object = MagicMock()
# Настраиваем возвращаемые значения для разных методов
mock_object.method_one.return_value = 10
mock_object.method_two.return_value = "hello"
mock_object.method_three.side_effect = [1, 2, 3] # Возвращает последовательно 1, 2, 3 при каждом вызове
# Проверяем работу мок-объекта
print(mock_object.method_one()) # Output: 10
print(mock_object.method_two()) # Output: hello
print(mock_object.method_three()) # Output: 1
print(mock_object.method_three()) # Output: 2
Создание сложных мок-объектов, имитирующих работу с несколькими методами, - важный навык при написании юнит-тестов. В Python для этого часто используют библиотеку unittest.mock
(или просто mock
для старых версий Python).
Основная идея заключается в создании экземпляра класса Mock
и настройке атрибутов этого экземпляра таким образом, чтобы они представляли различные методы, возвращающие предопределенные значения или выполняющие определенные действия.
Вот несколько распространенных подходов:
return_value
: Этот атрибут позволяет определить, что метод должен возвращать при каждом вызове.
from unittest.mock import Mock
# Создаем мок-объект
my_mock = Mock()
# Настраиваем return_value для метода 'method_a'
my_mock.method_a.return_value = 'Result from method_a'
# Настраиваем return_value для метода 'method_b'
my_mock.method_b.return_value = 42
# Теперь, когда мы вызываем эти методы, они возвращают заданные значения
print(my_mock.method_a()) # Output: Result from method_a
print(my_mock.method_b()) # Output: 42
side_effect
: Этот атрибут позволяет определить функцию, которая будет вызываться при каждом вызове метода. Функция может возвращать значение, выбросить исключение или выполнять другие действия.
from unittest.mock import Mock
def side_effect_function(arg1, arg2):
if arg1 > 10:
return arg1 * arg2
else:
raise ValueError("arg1 must be greater than 10")
my_mock = Mock()
my_mock.method_c.side_effect = side_effect_function
# Вызов с валидными аргументами
print(my_mock.method_c(15, 2)) # Output: 30
# Вызов с невалидными аргументами
try:
my_mock.method_c(5, 2)
except ValueError as e:
print(e) # Output: arg1 must be greater than 10
Mock
: Можно создавать вложенные мок-объекты и определять цепочки вызовов.
from unittest.mock import Mock
# Создаем мок-объект для внутреннего компонента
inner_mock = Mock()
inner_mock.calculate.return_value = 100
# Создаем основной мок-объект
outer_mock = Mock()
outer_mock.inner_component = inner_mock
# Функция, использующая inner_component
def my_function(obj):
return obj.inner_component.calculate() + 50
# Проверяем, что функция правильно использует мок-объект
result = my_function(outer_mock)
print(result) # Output: 150
inner_mock.calculate.assert_called_once()
PropertyMock
: Для мокирования свойств объекта, особенно когда нельзя напрямую присвоить значение.
from unittest.mock import Mock, PropertyMock
class MyClass:
@property
def my_property(self):
# Some complex calculation
return 10
my_object = MyClass()
# Мокируем свойство my_property
with patch('__main__.MyClass.my_property', new_callable=PropertyMock) as mock_my_property:
mock_my_property.return_value = 20
print(my_object.my_property) # Output: 20
Важно:
assert_called_once()
, assert_called_with()
и другие методы mock
для проверки, что методы были вызваны с ожидаемыми аргументами.
call_args_list
для анализа истории вызовов.
При проектировании тестов, стремитесь к тому, чтобы мок-объекты были максимально простыми и отражали только то поведение, которое необходимо для тестирования конкретного участка кода. Избегайте создания слишком сложных моков, которые могут сделать тесты хрупкими и сложными для понимания. Лучше разбить сложный тест на несколько более простых, каждый из которых проверяет определенный аспект поведения.