关于单元测试:在芹菜任务中模拟通话

Mocking out a call within a celery task

我有一个运行芹菜任务的烧瓶应用程序。 我正在尝试模拟在该任务中发生的单个API调用。

views.py

1
2
3
4
5
from mypackage.task_module import my_task
@app.route('/run_task')
def run_task():
    task = my_task.delay()
    return some_response

task_module.py

1
2
3
4
5
from mypackage.some_module import SomeClass

@celery.task
def my_task():
    return SomeClass().some_function()

some_module.py

1
2
3
4
5
6
7
8
9
from mypackage.xyz import external_service
class SomeClass(object):
    def some_function(self):
        #do some stuff
        result = external_service(some_param)
        if 'x' in result:
             #do something
        elif 'y' in result:
             #do something else

我想模拟result = external_service()行,以便可以触发第一个或第二个代码路径。

所以这就是我正在尝试的:

1
2
3
4
@mock.patch('mypackage.some_module.external_service', autospec=True)
def test_x_path(my_mock):
    my_mock.return_value = {'x': some_val}
    #run test, expect 'x' code path to run

但是,这是行不通的,因为(我认为)该修补程序发生在Flask的Python进程中,而不是Celery正在使用的修补程序。 模拟任务本身不起作用,因为我要测试的是外部服务返回'x''y'时任务的行为。

帮助将不胜感激。


一个不错的选择是在测试配置中将CELERY_ALWAYS_EAGER设置为True。这使对Celery的所有调用都同步。请参阅此选项的文档。使用此选项,您在Flask流程中设置的任何模拟都应在Celery任务中进行。

附带的好处是,您无需拥有Celery工人,因此简化了测试配置。

更新:在评论中进行讨论后,您似乎不想或不能摆脱Celery工作人员进行测试配置。在这种情况下,我可以提供三种解决方案,我认为它们可以满足您的需求:

  • 编写一个模拟您的Celery任务的远程控制命令,然后使测试代码通过broadcast()在所有工作程序上运行。

  • 为您的工作人员定义一个自定义命令行选项,例如--test。然后添加一个引导步骤,检查该参数并进行模拟。

  • 创建一个替代模块,以在-A命令行参数中为Celery工作者提供权限。这应该是原始模块的相同副本,但是添加了模拟功能。然后从此替代模块开始进行测试。

  • 我希望您能找到这三个选项之一!


    创建测试功能设置

    1
    2
    3
    4
    5
    6
    7
    8
    class TestCeleryTask(TestCase):
        def setUp(self):
             app.config['CELERY_ALWAYS_EAGER'] = True
             app.config['BROKER_BACKEND'] = 'memory'
             app.config['CELERY_EAGER_PROPAGATES_EXCEPTIONS'] = True

        def test_task(self):
             # test it