简体   繁体   English

如何使用模拟框架模拟龙卷风协程功能进行单元测试?

[英]How to mock a tornado coroutine function using mock framework for unit testing?

The title simply described my problem. 标题简单描述了我的问题。 I would like to mock "_func_inner_1" with specific return value. 我想用特定的返回值来模拟“_func_inner_1”。 Thanks for any advises :) 谢谢你的任何建议:)

code under test: 被测代码:

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)

But, this intuitive solution not work 但是,这种直观的解决方案无效

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)

With below error, seems _func_inner_1 is not patched due to it's coroutine nature 如果出现以下错误,则_func_inner_1似乎没有修补,因为它具有协同性质

AssertionError: 2

if I add coroutine to patch returned mock function 如果我添加coroutine补丁返回模拟功能

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

the error becomes: 错误变成:

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__

This is the closest solution I can find, but the mocking function will NOT be reset after test case execution, unlike what patch does 这是我能找到的最接近的解决方案,但是在测试用例执行后不会重置模拟函数,这与补丁不同

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

There are two issues here: 这里有两个问题:

First is the interaction between @mock.patch and @gen_test . 首先是@mock.patch@gen_test之间的交互。 gen_test works by converting a generator into a "normal" function; gen_test通过将生成器转换为“正常”函数来工作; mock.patch only works on normal functions (as far as the decorator can tell, the generator returns as soon as it reaches the first yield , so mock.patch undoes all its work). mock.patch仅适用于普通函数(就装饰者所知,生成器一到达第一个yield就会返回,因此mock.patch撤消其所有工作)。 To avoid this problem, you can either reorder the decorators (always put @mock.patch before @gen_test , or use the with form of mock.patch instead of the decorator form. 为了避免这个问题,你可以重新排序装饰器(总是在@mock.patch 之前放置@mock.patch @gen_test ,或者使用with形式的mock.patch而不是装饰器形式。

Second, coroutines should never raise an exception. 其次,协同程序不应该提出异常。 Instead, they return a Future which will contain a result or an exception. 相反,它们返回一个包含结果或异常的Future The special Return exception is encapsulated by the coroutine system; 特殊的Return异常由协程系统封装; you would never raise it from a Future. 你永远不会从未来提出它。 When you create your mocks, you must create the appropriate Future and set it as the return value instead of using side_effect to raise on exception. 创建模拟时,必须创建适当的Future并将其设置为返回值,而不是使用side_effect来引发异常。

The complete solution is: 完整的解决方案是:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM