Mock

advertisement
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 <= 0
return g(x) * g(-x)
else:
return 0
Что хочется протестировать?
Требуется убедиться, что функция 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)
<Mock name='mock()' id='40790112'>
Для его методов также создаются новые Мockобъекты:
>>> s = f.read()
>>> print(s)
<Mock name='mock().read()' id='40688664'>
НО!
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 patch
...
@patch('func.g', return_value = 3)
def test_f(self, mock_g):
result = f(5)
self.assertEqual(result, 9)
Преимущества Mock-объектов
1) Независимое тестирование одного метода
или класса
2) Скорость тестирования (не выполняется
лишний код)
3) Стимулирует к правильному проектированию
4) Избавление от недетерминированного
поведения
5) Генерация сложно воспроизводимых в
реальных условиях ошибок
6) Запись и анализ логов вызовов функций
Download