Как насмехаться с функцией торнадо coroutine с использованием макетной рамки для модульного тестирования?

Название просто описывает мою проблему. Я хотел бы высмеять "_func_inner_1" с конкретным возвращаемым значением. Спасибо за любые советы:)

проверенный код:

from tornado.gen import coroutine, Return
from tornado.testing import gen_test
from tornado.testing import AsyncTestCase

import mock

@coroutine
def _func_inner_1():
    raise Return(1)

@coroutine
def _func_under_test_1():
    temp = yield _func_inner_1()
    raise Return(temp + 1)

Но это интуитивное решение не работает

class Test123(AsyncTestCase):

    @gen_test
    @mock.patch(__name__ + '._func_inner_1')
    def test_1(self, mock_func_inner_1):
        mock_func_inner_1.side_effect = Return(9)
        result_1 = yield _func_inner_1()
        print 'result_1', result_1
        result = yield _func_under_test_1()
        self.assertEqual(10, result, result)

С ошибкой ниже, кажется, что _func_inner_1 не исправлен из-за этого coroutine nature

AssertionError: 2

если я добавляю coroutine для исправления возвращенной функции mock

@gen_test
@mock.patch(__name__ + '._func_inner_1')
def test_1(self, mock_func_inner_1):
    mock_func_inner_1.side_effect = Return(9)
    mock_func_inner_1 = coroutine(mock_func_inner_1)
    result_1 = yield _func_inner_1()
    print 'result_1', result_1
    result = yield _func_under_test_1()
    self.assertEqual(10, result, result)

ошибка становится:

Traceback (most recent call last):
  File "tornado/testing.py", line 118, in __call__
    result = self.orig_method(*args, **kwargs)
  File "tornado/testing.py", line 494, in post_coroutine
    timeout=timeout)
  File "tornado/ioloop.py", line 418, in run_sync
    return future_cell[0].result()
  File "tornado/concurrent.py", line 109, in result
    raise_exc_info(self._exc_info)
  File "tornado/gen.py", line 175, in wrapper
    yielded = next(result)
  File "coroutine_unit_test.py", line 39, in test_1
    mock_func_inner_1 = coroutine(mock_func_inner_1)
  File "tornado/gen.py", line 140, in coroutine
    return _make_coroutine_wrapper(func, replace_callback=True)
  File "tornado/gen.py", line 150, in _make_coroutine_wrapper
    @functools.wraps(func)
  File "functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
  File "mock.py", line 660, in __getattr__
    raise AttributeError(name)
AttributeError: __name__

Это самое близкое решение, которое я могу найти, но функция mocking не будет reset после выполнения тестового примера, в отличие от того, что патч

@gen_test
def test_4(self):
    global _func_inner_1
    mock_func_inner_1 = mock.create_autospec(_func_inner_1)
    mock_func_inner_1.side_effect = Return(100)
    mock_func_inner_1 = coroutine(mock_func_inner_1)
    _func_inner_1 = mock_func_inner_1
    result = yield _func_under_test_1()
    self.assertEqual(101, result, result) 

Ответы

Ответ 1

Здесь есть две проблемы:

Сначала это взаимодействие между @mock.patch и @gen_test. gen_test работает путем преобразования генератора в "нормальную" функцию; mock.patch работает только на обычных функциях (насколько может сказать декоратор, генератор возвращается, как только он достигает первого yield, поэтому mock.patch отменяет всю свою работу). Чтобы избежать этой проблемы, вы можете либо изменить порядок декораторов (всегда ставьте @mock.patch до @gen_test), либо используйте форму with mock.patch вместо формы декоратора.

Во-вторых, сопрограммы никогда не должны создавать исключение. Вместо этого они возвращают Future, который будет содержать результат или исключение. Специальное исключение Return инкапсулировано системой сопрограммы; вы никогда не поднимете его из Будущего. Когда вы создаете свои mocks, вы должны создать соответствующее Будущее и установить его как возвращаемое значение вместо использования side_effect для повышения на исключение.

Полное решение:

from tornado.concurrent import Future
from tornado.gen import coroutine, Return
from tornado.testing import gen_test
from tornado.testing import AsyncTestCase

import mock

@coroutine
def _func_inner_1():
    raise Return(1)

@coroutine
def _func_under_test_1():
    temp = yield _func_inner_1()
    raise Return(temp + 1)

class Test123(AsyncTestCase):

    @mock.patch(__name__ + '._func_inner_1')
    @gen_test
    def test_1(self, mock_func_inner_1):
        future_1 = Future()
        future_1.set_result(9)
        mock_func_inner_1.return_value = future_1
        result_1 = yield _func_inner_1()
        print 'result_1', result_1
        result = yield _func_under_test_1()
        self.assertEqual(10, result, result)

import unittest
unittest.main()