[英]Python unit test failed because of StopIteration error from Mock
我正在使用 ccxt.binance 模擬測試 class object。 這個模擬的 object 作為參數傳遞給我正在測試的 function。 最初它是有效的,因為我能夠在一定程度上進行測試。 但是,當我更改 object 的值時,function 不遵守該值並返回 StopIteration 錯誤。 我在模擬設置中遺漏了什么嗎? 這是代碼的示例片段。
這是正在測試的代碼
def exchange_history_load(args, ccxt_exchange):
# skip exchange if has no true fetchOHLCV or if exchange has no timeframe specific data
if ((not ccxt_exchange.has['fetchOHLCV']) or (ccxt_exchange.has['fetchOHLCV'] == 'emulated') or
(not ccxt_exchange.timeframes.get(args.timeframe))):
logging.warning(f'Skipping {ccxt_exchange.name}.')
return
# check if exchange is in database. Write to database if not.
exchange_db_data = get_exchange(ccxt_exchange.id)
if not exchange_db_data:
insert_exchange_to_db(ccxt_exchange.id, ccxt_exchange.name)
logging.info(f'Inserted {ccxt_exchange.name} to database.')
exchange_db_data = get_exchange(ccxt_exchange.id)
exchange_db_id = exchange_db_data[0]
logging.info(f'Fetched {ccxt_exchange.name} data.')
# load exchange markets
markets = ccxt_exchange.load_markets()
logging.info(f'Loaded {ccxt_exchange.name} markets.')
return exchange_db_id, markets
這是測試 function
@patch('src.cron.historical_price.hp_load_data.insert_exchange_to_db')
@patch('src.cron.historical_price.hp_load_data.get_exchange')
@patch('ccxt.binance')
def test_exchange_history_load(mock_binance, mock_get_exchange, mock_insert_exchange_to_db):
# create sample arguments for function
args = argparse.Namespace(timeframe='1d')
# mock up sample return values for necessary functions
mock_get_exchange.side_effect = [None, (1, 'binance', 'Binance'), (1, 'binance', 'Binance')]
mock_binance.return_value.has.return_value = {'fetchOHLCV': True}
mock_binance.return_value.load_markets.return_value = {
'BTC/USDT': {
'id': 'btcusdt',
'symbol': 'BTC/USDT',
'base': 'BTC',
'quote': 'USDT',
'active': True
}
}
mock_binance.return_value.id.return_value = 'binance'
mock_binance.return_value.name.return_value = 'Binance'
ccxt_exchange = ccxt.binance()
# case 1: exchange has fetchOHLCV, timeframe, but is not in database
# test assertions with case 1
exchange_db_id, markets = exchange_history_load(args, ccxt_exchange)
assert type(exchange_db_id) == int
assert type(markets) == dict
assert mock_get_exchange.call_count == 2
calls = [call(mock_binance.return_value.id), call(mock_binance.return_value.id)]
mock_get_exchange.assert_has_calls(calls)
mock_insert_exchange_to_db.assert_called_once()
mock_binance.return_value.load_markets.assert_called_once()
# case 2: exchange has fetchOHLCV, timeframe, and is in database
# reset mock calls for case 2
mock_insert_exchange_to_db.reset_mock()
mock_get_exchange.reset_mock()
mock_binance.return_value.load_markets.reset_mock()
# test assertions with case 2
exchange_db_id, markets = exchange_history_load(args, ccxt_exchange)
assert type(exchange_db_id) == int
assert type(markets) == dict
mock_get_exchange.assert_called_once()
assert not mock_insert_exchange_to_db.called
mock_binance.return_value.load_markets.assert_called_once()
calls = [call(mock_binance.return_value.id)]
mock_get_exchange.assert_has_calls(calls)
# case 3: exchange doesn't have true fetchOHLCV
# reset_mock calls for case 3
mock_get_exchange.reset_mock()
mock_insert_exchange_to_db.reset_mock()
mock_binance.reset_mock()
mock_binance.return_value.has.return_value = {'fetchOHLCV': True}
# mock_binance.return_value.load_markets.reset_mock()
# test assertions with case 3
exchange_db_id, markets = exchange_history_load(args, ccxt_exchange)
# assert not exchange_db_id
# assert not markets
assert not mock_get_exchange.called
assert not mock_insert_exchange_to_db.called
assert not mock_binance.return_value.load_markets.called
案例 1 和案例 2 有效。 但是對於情況 3,會拋出一個錯誤。
mock_binance = <MagicMock name='binance' id='140285596974272'>
mock_get_exchange = <MagicMock name='get_exchange' id='140285596584312'>
mock_insert_exchange_to_db = <MagicMock name='insert_exchange_to_db' id='140285596584480'>
@patch('src.cron.historical_price.hp_load_data.insert_exchange_to_db')
@patch('src.cron.historical_price.hp_load_data.get_exchange')
@patch('ccxt.binance')
def test_exchange_history_load(mock_binance, mock_get_exchange, mock_insert_exchange_to_db):
# create sample arguments for function
args = argparse.Namespace(timeframe='1d')
# mock up sample return values for necessary functions
mock_get_exchange.side_effect = [None, (1, 'binance', 'Binance'), (1, 'binance', 'Binance')]
mock_binance.return_value.has.return_value = {'fetchOHLCV': True}
mock_binance.return_value.load_markets.return_value = {
'BTC/USDT': {
'id': 'btcusdt',
'symbol': 'BTC/USDT',
'base': 'BTC',
'quote': 'USDT',
'active': True
}
}
mock_binance.return_value.id.return_value = 'binance'
mock_binance.return_value.name.return_value = 'Binance'
ccxt_exchange = ccxt.binance()
# case 1: exchange is not in database
# test assertions with case 1
exchange_db_id, markets = exchange_history_load(args, ccxt_exchange)
assert type(exchange_db_id) == int
assert type(markets) == dict
assert mock_get_exchange.call_count == 2
calls = [call(mock_binance.return_value.id), call(mock_binance.return_value.id)]
mock_get_exchange.assert_has_calls(calls)
mock_insert_exchange_to_db.assert_called_once()
mock_binance.return_value.load_markets.assert_called_once()
# case 2: exchange is in database
# reset mock calls for case 2
mock_insert_exchange_to_db.reset_mock()
mock_get_exchange.reset_mock()
mock_binance.return_value.load_markets.reset_mock()
# test assertions with case 2
exchange_db_id, markets = exchange_history_load(args, ccxt_exchange)
assert type(exchange_db_id) == int
assert type(markets) == dict
mock_get_exchange.assert_called_once()
assert not mock_insert_exchange_to_db.called
mock_binance.return_value.load_markets.assert_called_once()
calls = [call(mock_binance.return_value.id)]
mock_get_exchange.assert_has_calls(calls)
# case 3: exchange doesn't have true fetchOHLCV
# reset_mock calls for case 3
mock_get_exchange.reset_mock()
mock_insert_exchange_to_db.reset_mock()
mock_binance.reset_mock()
mock_binance.return_value.has.return_value = {'fetchOHLCV': True}
# mock_binance.return_value.load_markets.reset_mock()
# test assertions with case 3
> exchange_db_id, markets = exchange_history_load(args, ccxt_exchange)
test_cron/test_historical_price/test_hp_load_data.py:89:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../src/cron/historical_price/hp_load_data.py:75: in exchange_history_load
exchange_db_data = get_exchange(ccxt_exchange.id)
/usr/lib/python3.6/unittest/mock.py:939: in __call__
return _mock_self._mock_call(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_mock_self = <MagicMock name='get_exchange' id='140285596584312'>
args = (<MagicMock name='binance().id' id='140285596659328'>,), kwargs = {}
self = <MagicMock name='get_exchange' id='140285596584312'>, _new_name = ''
_new_parent = None
_call = call(<MagicMock name='binance().id' id='140285596659328'>), seen = set()
skip_next_dot = False, do_method_calls = False, name = 'get_exchange'
def _mock_call(_mock_self, *args, **kwargs):
self = _mock_self
self.called = True
self.call_count += 1
_new_name = self._mock_new_name
_new_parent = self._mock_new_parent
_call = _Call((args, kwargs), two=True)
self.call_args = _call
self.call_args_list.append(_call)
self.mock_calls.append(_Call(('', args, kwargs)))
seen = set()
skip_next_dot = _new_name == '()'
do_method_calls = self._mock_parent is not None
name = self._mock_name
while _new_parent is not None:
this_mock_call = _Call((_new_name, args, kwargs))
if _new_parent._mock_new_name:
dot = '.'
if skip_next_dot:
dot = ''
skip_next_dot = False
if _new_parent._mock_new_name == '()':
skip_next_dot = True
_new_name = _new_parent._mock_new_name + dot + _new_name
if do_method_calls:
if _new_name == name:
this_method_call = this_mock_call
else:
this_method_call = _Call((name, args, kwargs))
_new_parent.method_calls.append(this_method_call)
do_method_calls = _new_parent._mock_parent is not None
if do_method_calls:
name = _new_parent._mock_name + '.' + name
_new_parent.mock_calls.append(this_mock_call)
_new_parent = _new_parent._mock_new_parent
# use ids here so as not to call __hash__ on the mocks
_new_parent_id = id(_new_parent)
if _new_parent_id in seen:
break
seen.add(_new_parent_id)
ret_val = DEFAULT
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
raise effect
if not _callable(effect):
> result = next(effect)
E StopIteration
/usr/lib/python3.6/unittest/mock.py:998: StopIteration
對於那些尋求答案的人。 當您的模擬之一的side_effect
在n
> len(side_effect)
中被調用n
次時,就會出現此問題。
my_mock = MagicMock()
my_mock.side_effect = [1, 2]
...
n = 3
for i in range(n):
my_mock()
這將觸發StopIteration
錯誤。
您對ccxt.binance進行的Value更改導致對象ccxt_exchange超出范圍。 因此,例如,給出“ StopIteration”錯誤-
In [1]: from fleeteng_fw_agent.hostinfo imfrom mock import MagicMock,
PropertyMock, call, patch
In [2]: mock_get_exchange = MagicMock()
In [3]: mock_get_exchange.side_effect = [None, (1, 'binance', 'Binance'), (1,
'binance', 'Binance')]
In [4]: mock_get_exchange
Out[4]: <MagicMock id='4310663120'>
In [5]: mock_get_exchange()
In [6]: mock_get_exchange()
Out[6]: (1, 'binance', 'Binance')
In [7]: mock_get_exchange()
Out[7]: (1, 'binance', 'Binance')
In [8]: mock_get_exchange()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-8-4d63b6292e10> in <module>()
----> 1 mock_get_exchange()
/Users/**/ in __call__(_mock_self, *args, **kwargs)
954 # in the signature
955 _mock_self._mock_check_sig(*args, **kwargs)
--> 956 return _mock_self._mock_call(*args, **kwargs)
957
958
/Users/**/ in _mock_call(_mock_self, *args, **kwargs)
1012
1013 if not _callable(effect):
-> 1014 result = next(effect)
1015 if _is_exception(result):
1016 raise result
StopIteration:
我將需要查看ccxt.binancegive的值更改,以為您提供確切的錯誤原因。 但我認為上述示例應將您引導到根本原因。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.