如何使用模拟框架模拟龙卷风协程函数进行单元测试?
How to mock a tornado coroutine function using mock framework for unit testing?
标题只是描述了我的问题。我想模拟具有特定 return 值的“_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 由于协程性质而未修补
AssertionError: 2
如果我添加协程来修补 returned 模拟函数
@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__
这是我能找到的最接近的解决方案,但模拟函数不会在测试用例执行后重置,这与补丁所做的不同
@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)
这里有两个问题:
首先是@mock.patch
和@gen_test
之间的互动。 gen_test 通过将生成器转换为 "normal" 函数来工作; mock.patch 仅适用于普通函数(据装饰器所知,生成器 return 到达第一个 yield
后立即生效,因此 mock.patch 撤消其所有工作).为避免此问题,您可以重新排序装饰器(始终将 @mock.patch
放在 @gen_test
之前,或者使用 with
形式的 mock.patch
而不是装饰器形式。
其次,协程不应该引发异常。相反,它们 return a Future
将包含结果或异常。特殊的Return
异常由协程系统封装;你永远不会从 Future 中提出它。创建模拟时,必须创建适当的 Future 并将其设置为 return 值,而不是使用 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()
标题只是描述了我的问题。我想模拟具有特定 return 值的“_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 由于协程性质而未修补
AssertionError: 2
如果我添加协程来修补 returned 模拟函数
@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__
这是我能找到的最接近的解决方案,但模拟函数不会在测试用例执行后重置,这与补丁所做的不同
@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)
这里有两个问题:
首先是@mock.patch
和@gen_test
之间的互动。 gen_test 通过将生成器转换为 "normal" 函数来工作; mock.patch 仅适用于普通函数(据装饰器所知,生成器 return 到达第一个 yield
后立即生效,因此 mock.patch 撤消其所有工作).为避免此问题,您可以重新排序装饰器(始终将 @mock.patch
放在 @gen_test
之前,或者使用 with
形式的 mock.patch
而不是装饰器形式。
其次,协程不应该引发异常。相反,它们 return a Future
将包含结果或异常。特殊的Return
异常由协程系统封装;你永远不会从 Future 中提出它。创建模拟时,必须创建适当的 Future 并将其设置为 return 值,而不是使用 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()