出于单元测试的目的,我创建了一个类,其实例是一个可迭代的类,它将产生一个特定的序列然后引发一个异常:
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
属性时,为什么要重新发明轮子呢?根据documentation,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
属性中来解决这个“bug”:
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
的一个特征是否带有意想不到的后果?
我同意这确实是一个错误。虽然这是一个边缘案例。
正如我们在源代码中看到的那样。 mock
模块期望iter(ret_val)
将返回未更改的迭代器,如果ret_val
已经是迭代器。
嗯,实际上它确实需要调用ret_val
的__iter__
方法。