如何使用模拟框架模拟龙卷风协程函数进行单元测试? [英] How to mock a tornado coroutine function using mock framework for unit testing?

查看:42
本文介绍了如何使用模拟框架模拟龙卷风协程函数进行单元测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

标题只是描述了我的问题.我想用特定的返回值模拟"_func_inner_1".感谢您的任何建议:)

The title simply described my problem. I would like to mock "_func_inner_1" with specific return value. Thanks for any advises :)

正在测试的代码:

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)

出现以下错误,由于协程性质,似乎_func_inner_1没有被修补

With below error, seems _func_inner_1 is not patched due to it's coroutine nature

AssertionError: 2

如果我将协程添加到补丁返回的模拟功能中

if I add coroutine to patch returned mock function

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

这是我能找到的最接近的解决方案,但与补丁程序不同,模拟功能在测试用例执行后将不会重置

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) 

推荐答案

这里有两个问题:

首先是@mock.patch@gen_test之间的交互. gen_test通过将生成器转换为正常"函数来工作; mock.patch仅适用于正常功能(就装饰者所知,生成器在到达第一个yield时立即返回,因此,mock.patch撤消了其所有工作).为避免此问题,您可以重新排列装饰器的顺序(始终将@mock.patch 放在 @gen_test之前,或使用mock.patchwith形式而不是装饰器形式.

First is the interaction between @mock.patch and @gen_test. gen_test works by converting a generator into a "normal" function; 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). 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.

第二,协程不应该引发异常.相反,它们返回Future,其中将包含结果或异常.特殊的Return异常由协程系统封装;您将永远不会从未来中提出它.创建模拟时,必须创建适当的Future并将其设置为返回值,而不是使用side_effect引发异常.

Second, coroutines should never raise an exception. Instead, they return a Future which will contain a result or an exception. The special Return exception is encapsulated by the coroutine system; 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.

完整的解决方案是:

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

这篇关于如何使用模拟框架模拟龙卷风协程函数进行单元测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆