python unit-testing mocking tornado

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()