簡體   English   中英

Python 單元測試失敗,因為來自 Mock 的 StopIteration 錯誤

[英]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_effectn > 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM