【问题标题】:In-Memory broker for celery unit testscelery 单元测试的内存中代理
【发布时间】:2014-04-09 15:09:36
【问题描述】:

我有一个用 Django 编写的 REST API,其端点在发布到芹菜任务时会对其进行排队。响应包含我想用来测试任务是否已创建并获得结果的任务 ID。所以,我想做这样的事情:

def test_async_job():
    response = self.client.post("/api/jobs/", some_test_data, format="json")
    task_id = response.data['task_id']
    result = my_task.AsyncResult(task_id).get()
    self.assertEquals(result, ...)

我显然不想让 celery worker 来运行单元测试,我希望以某种方式模拟它。我不能使用 CELERY_ALWAYS_EAGER,因为这似乎完全绕过了代理,阻止我使用 AsyncResult 通过其 ID 获取任务(如 here 所述)。

通过 celery 和 kombu docs,我发现有一个用于单元测试的内存中传输,可以满足我的需求。我尝试覆盖 BROKER_URL 设置以在测试中使用它:

@override_settings(BROKER_URL='memory://')
def test_async_job():

但行为与 ampq 代理相同:它阻塞测试等待结果。知道我应该如何配置此代理以使其在测试中正常工作吗?

【问题讨论】:

  • 即使有内存中的代理,你仍然需要一个工人。不幸的是,我认为你想做的事情是不可能的。您要么需要启动一个工作人员来使用您的测试,要么使用 CELERY_ALWAYS_EAGER 同步运行任务(在这种情况下,正如您所发现的,您不会得到 AsyncResult)。
  • 为什么需要通过 ID 访问任务?这看起来是单元测试的好候选。为什么不直接测试生成任务的函数,而不是通过 HTTP 进行测试呢?这样您就可以得到与AsyncResult 具有相同API 的EagerResult
  • 如果您使用的是 Python 2,您可以使用 django-supervisor 启动 Celery 进行单元测试(并在代码更改时自动重启)。不幸的是,它与 Python 3 不兼容。(意识到这不是您要的,但万一它对任何人都有用。)

标签: django celery kombu


【解决方案1】:

您可以在设置中指定 broker_backend :

if 'test' in sys.argv[1:]:
    BROKER_BACKEND = 'memory'
    CELERY_TASK_ALWAYS_EAGER = True
    CELERY_TASK_EAGER_PROPAGATES = True

或者您可以直接在测试中使用装饰器覆盖设置

import unittest
from django.test.utils import override_settings


class MyTestCase(unittest.TestCase):

    @override_settings(CELERY_TASK_EAGER_PROPAGATES=True,
                       CELERY_TASK_ALWAYS_EAGER=True,
                       BROKER_BACKEND='memory')
    def test_mytask(self):
        ...

【讨论】:

【解决方案2】:

您可以使用 Kombu 内存代理来运行单元测试,但是要这样做,您需要使用与 Django 服务器相同的 Celery 应用程序对象来启动一个 Celery 工作者。

要使用内存代理,请将 BROKER_URL 设置为 memory://localhost/

然后,要启动一个小型芹菜工人,您可以执行以下操作:

app = <Django Celery App>

# Set the worker up to run in-place instead of using a pool
app.conf.CELERYD_CONCURRENCY = 1
app.conf.CELERYD_POOL = 'solo'

# Code to start the worker
def run_worker():
    app.worker_main()

# Create a thread and run the worker in it
import threading
t = threading.Thread(target=run_worker)
t.setDaemon(True)
t.start()

您需要确保使用与 Django celery 应用实例相同的应用。

注意启动worker会打印很多东西并修改日志设置。

【讨论】:

  • 请注意,app.worker_main() 从命令行选择参数,无论是运行测试/脚本,这可能不是你想要运行 Celery。
【解决方案3】:

这是一个功能更全面的 Django TransactionTestCase 示例,它适用于 Celery 4.x。

import threading

from django.test import TransactionTestCase
from django.db import connections

from myproj.celery import app  # your Celery app


class CeleryTestCase(TransactionTestCase):
    """Test case with Celery support."""

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        app.control.purge()
        cls._worker = app.Worker(app=app, pool='solo', concurrency=1)
        connections.close_all()
        cls._thread = threading.Thread(target=cls._worker.start)
        cls._thread.daemon = True
        cls._thread.start()

    @classmethod
    def tearDownClass(cls):
        cls._worker.stop()
        super().tearDownClass()

请注意,这不会将您的队列名称更改为测试队列,因此,如果您也在运行该应用程序,您也应该这样做。

【讨论】:

  • 很好的答案!它可以防止我们使用伪造工人的task_always_eager 选项。要访问当前的 celery 应用程序,实际上只需 from celery import current_app 即可。
  • 有什么理由选择TransactionTestCase而不是普通的测试用例?
  • @danielle 这很遗憾对我不起作用:我得到 conn_errors = self.channel.connection.client.connection_errors AttributeError: 'NoneType' object has no attribute 'client'
  • @Jonathan 是的,如果您没有真正的事务,您将无法在线程之间共享数据(即无法实际提交)。
猜你喜欢
  • 1970-01-01
  • 2020-06-24
  • 1970-01-01
  • 2011-09-28
  • 2010-12-10
  • 2022-11-14
  • 2023-04-09
  • 2013-06-13
  • 2011-09-28
相关资源
最近更新 更多