python - ¿Cómo simular una función de coroutine tornado utilizando el marco simulado para la prueba de unidad?
unit-testing mocking (1)
El título simplemente describía mi problema. Me gustaría burlarme de "_func_inner_1" con un valor de retorno específico. Gracias por cualquier consejo :)
código bajo prueba:
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)
Pero, esta solución intuitiva no funciona.
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)
Con el siguiente error, parece que _func_inner_1 no está parcheado debido a su naturaleza natural.
AssertionError: 2
Si agrego coroutine al parche devuelto la función simulada
@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)
el error se convierte en:
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__
Esta es la solución más cercana que puedo encontrar, pero la función de simulación no se restablecerá después de la ejecución del caso de prueba, a diferencia de lo que hace el parche
@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)
Hay dos problemas aquí:
Primero está la interacción entre @mock.patch
y @gen_test
. gen_test funciona al convertir un generador en una función "normal"; mock.patch solo funciona en funciones normales (por lo que el decorador puede decir, el generador regresa tan pronto como alcanza el primer yield
, por lo que mock.patch deshace todo su trabajo). Para evitar este problema, puede reordenar los decoradores (siempre ponga @mock.patch
antes de @gen_test
, o use la forma with
mock.patch
lugar de la forma de decorador.
En segundo lugar, los coroutines nunca deben plantear una excepción. En su lugar, devuelven un Future
que contendrá un resultado o una excepción. La excepción de Return
especial es encapsulada por el sistema coroutine; Nunca lo levantarías de un futuro. Cuando cree sus simulacros, debe crear el Futuro apropiado y configurarlo como el valor de retorno en lugar de usar side_effect para provocar una excepción.
La solución completa es:
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()