Для целей модульного теста я создал класс, экземпляр которого является итеративным, который выдает определенную последовательность, а затем вызывает исключение:
class Iter:
def __init__(self, seq):
self.seq = seq
self.pos = 0
def __next__(self):
if self.pos == len(self.seq):
raise Exception
value = self.seq[self.pos]
self.pos += 1
return value
def __iter__(self):
return self
так что:
for value in Iter((1, 2, 3)):
print(value)
будет выводить:
1
2
3
Traceback (most recent call last):
File "test.py", line 25, in <module>
for value in mocked_iterable:
File "test.py", line 11, in __next__
raise Exception
Exception
Но зачем изобретать велосипед, если MagicMock
уже имеет атрибут side_effect
, который должен делать то же самое? Согласно документации , атрибут side_effect
может быть итерируемым, который возвращает либо значение, которое будет возвращено при вызове макета, либо исключение для поднятия, поэтому он подходит для цели имитации вышеупомянутого класса в совершенстве. Поэтому я создал объект MagicMock
и сделал так, чтобы его метод __iter__
возвращал сам объект, и сделал метод __next__
, чтобы получить побочный эффект желаемой последовательности и исключения:
from unittest.mock import MagicMock
mocked_iterable = MagicMock()
mocked_iterable.__iter__.return_value = mocked_iterable
mocked_iterable.__next__.side_effect = [1, 2, 3, Exception]
for value in mocked_iterable:
print(value)
Однако, это выводит:
...
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 1005, in _mock_call
ret_val = effect(*args, **kwargs)
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 1793, in __iter__
return iter(ret_val)
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 939, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 944, in _mock_call
self.called = True
RecursionError: maximum recursion depth exceeded
Но вопрос в том, почему существует какая-либо рекурсия?
Я обнаружил, что могу обойти эту "ошибку", поместив ссылку на себя в атрибут __iter__
side_effect
вместо:
mocked_iterable = MagicMock()
mocked_iterable.__iter__.side_effect = [mocked_iterable]
mocked_iterable.__next__.side_effect = [1, 2, 3, Exception]
for value in mocked_iterable:
print(value)
Это правильно выводит:
1
2
3
Traceback (most recent call last):
File "test.py", line 6, in <module>
for value in mocked_iterable:
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 939, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 1000, in _mock_call
raise result
Exception
Но действительно ли ошибка рекурсии является ошибкой или функцией mock
с непредвиденными последствиями?