RecursionError при попытке смоделировать итерацию с помощью метода __iter__, который возвращает self - PullRequest
0 голосов
/ 20 апреля 2019

Для целей модульного теста я создал класс, экземпляр которого является итеративным, который выдает определенную последовательность, а затем вызывает исключение:

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 с непредвиденными последствиями?

1 Ответ

0 голосов
/ 20 апреля 2019

Я согласен, что это действительно ошибка. Хотя это крайний случай.

Как мы видим в исходном коде. Модуль mock ожидает, что iter(ret_val) вернет неизмененный итератор, если ret_val уже был итератором.

Ну, на самом деле это так, но все еще нужно вызвать __iter__ *1009* метод.

...