Mock-объекты mock (англ.) – ложный, фиктивный, мнимый, фальшивый, поддельный
Первый пример Мы хотим протестировать функцию f: def f(x): return g(x) * g(x)
НО: 1)Функция g еще не написана
ИЛИ: 2) Функция g работает недетерминировано: def g(x): return randint(5 * x)
ИЛИ: 3) Функция g слишком сложна, в ней тоже могут быть ошибки. Хотим протестировать f независимо
ИЛИ: 4) Функция g имеет нежелательные при тестировании побочные эффекты: def g(x): file = open(1.txt, w) file.write(blablabla)
Простейшее решение Пишем заглушку (stub): def g(x): return 5 ИДЕЯ: заменить настоящую функцию g на более удобную при тестировании
Более сложный пример def f(x): if x > 0 and x < 10: return g(x) elif x
Что хочется протестировать? Требуется убедиться, что функция f вызывает функцию g только в нужных случаях и нужное количество раз. ИДЕЯ: Наша фальшивая g не только возвращает фиксированное значение, но и запоминает информацию о своих вызовах.
Что для этого нужно? 1)Использовать g как объект (хранить состояние) 2)Чтобы объект можно было вызвать – () – он должен быть callable
Как это должно выглядеть? g = … f(5) g.assert_called_once_with(5) f(-5) g.assert_any_call(5) g.assert_any_call(-5)
Стандартный модуль unittest.mock (начиная с python3.3) from unittest.mock import Mock g = Mock(return_value = 5) …
Мокаем метод объекта def is_empty(group): return len(group.members()) == 0 group1 = Group() group1.members = Mock(return_value= [aaa, bbb, ccc]) is_empty(group1) group1.members.assert_called_with()
Мокаем объект group1 = Mock() group1.members.return_value = [aaa, bbb, ccc] Все необходимые методы/поля создаются на лету: group1.count()
НО: mock.__str__.return_value = foo вызовет исключение magic-методы (__xxx__) Mock не создает (они часто используются неявно, например, __str__, __eq__ и их автоматическое переопределение не всегда желательно)
MagicMock Аналог Mock, дополнительно позволяющий создавать magic-методы from unittest.mock import MagicMock m = MagicMock() m.__str__.return_value = 'foo' print(m) Выведет foo
Объекты из стандартных библиотек …тоже можно мокать: from unittest.mock import Mock import random random.randint = Mock(return_value = 100) print(random.randint(10))
Мокать можно всё from unittest.mock import Mock print = Mock() print('***')
Mock-объект хранит историю вызовов >>> mock = Mock(return_value=None) >>> mock() >>> mock(3, 4) >>> mock(key='fish', next='w00t!') >>> mock.call_args_list [call(), call(3, 4), call(key='fish', next='w00t!')]
…которую можно сравнивать с ожидаемыми значениями [call(), call(3, 4), call(key='fish', next='w00t!')] >>> expected = [(), ((3, 4),), ({'key':'fish', 'next':'w00t!'},)] >>> mock.call_args_list == expected True
Если return_value не определено … то возвращается Mock-объект: >>> open = Mock() >>> f = open("1.txt") >>> print(f) Для его методов также создаются новые Мock- объекты: >>> s = f.read() >>> print(s)
НО! from unittest.mock import Mock open = Mock() with open("1.txt") as f: s = f.read() вызовет исключение: AttributeError: __exit__
Замена на MagicMock лечит но проблемы остаются… Решение из коробки: from unittest.mock import mock_open open = mock_open(read_data='To be or...') with open("1.txt") as f: print(f.read()) print(open.mock_calls)
Выведет To be or... [call('1.txt'), call().__enter__(), call().read(), call().__exit__(None, None, None)]
Преимущества mock_open 1)Корректно работает и вместо отдельного open, и в конструкции с with 2)Необязательный параметр read_data (только для read!!!) 3)поддерживает только стандартный методы для файлов и выдает исключения для остальных: >>> f.aaa() AttributeError: Mock object has no attribute 'aaa'
patch func.py: def f(x): return g(x) * g(x) def g(x):...
func_test.py import unittest from func import f class FTest(unittest.TestCase): def test_f(self): result = f(5) self.assertEqual(result, 9) if __name__ == '__main__': unittest.main()
patch from unittest.mock import return_value = 3) def test_f(self, mock_g): result = f(5) self.assertEqual(result, 9)
Преимущества Mock-объектов 1)Независимое тестирование одного метода или класса 2)Скорость тестирования (не выполняется лишний код) 3)Стимулирует к правильному проектированию 4)Избавление от недетерминированного поведения 5)Генерация сложно воспроизводимых в реальных условиях ошибок 6)Запись и анализ логов вызовов функций