使用django-celery进行单元测试?


82

我正在为我们的django-celery项目想出一种测试方法。我已经阅读了文档中的注释,但是并没有使我对实际操作有个好主意。我并不担心在实际的守护进程测试任务,只是功能我的代码。我主要是想知道:

  1. task.delay()在测试过程中我们如何绕过(我尝试设置,CELERY_ALWAYS_EAGER = True但没有影响)?
  2. 在不实际更改settings.py的情况下,我们如何使用建议的测试设置(如果这是最好的方法)?
  3. 我们仍然可以使用manage.py test还是必须使用自定义运行器?

总体而言,有关芹菜测试的任何提示或技巧都将非常有帮助。


1
你的意思CELERY_ALWAYS_EAGER是什么都没有?
asksol 2010年

我仍然收到有关无法联系Rabbitmq的错误。
杰森·韦伯

你有回溯吗?我想除了.delay尝试建立连接以外的其他方法。
asksol

11
BROKER_BACKEND=memory在这种情况下,设置可能会有所帮助。
asksol

问你是对的。 BROKER_BACKEND=memory固定它。如果您将其作为答案,我会标记为正确。
詹森·韦伯

Answers:



72

我喜欢在需要芹菜结果才能完成的测试中使用override_settings装饰器。

from django.test import TestCase
from django.test.utils import override_settings
from myapp.tasks import mytask

class AddTestCase(TestCase):

    @override_settings(CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
                       CELERY_ALWAYS_EAGER=True,
                       BROKER_BACKEND='memory')
    def test_mytask(self):
        result = mytask.delay()
        self.assertTrue(result.successful())

如果要将其应用于所有测试,则可以使用celery测试运行程序,如http://docs.celeryproject.org/en/2.5/django/unit-testing.html所述,该基本设置了除(BROKER_BACKEND = 'memory')以外的相同设置。

在设置中:

TEST_RUNNER = 'djcelery.contrib.test_runner.CeleryTestSuiteRunner'

查看CeleryTestSuiteRunner的源代码,很清楚正在发生什么。


1
即使芹菜田从这里
shadi

适用于芹菜3.1。我只是让Celery测试用例从这个装饰器的父类继承而来。这样一来,它只需要放在一个地方,而无需拉入djcelery
kontextify

1
这在Celery 4.4上效果很好。和Django 2.2。到目前为止,我发现了运行单元测试的最佳方法。
Erik Kalkoken,

18

这是我的测试基类的摘录,该摘录对apply_async方法进行了存根并记录了对该方法的调用(包括)Task.delay。虽然有点粗略,但是在过去几个月中,它已经能够满足我的需求。

from django.test import TestCase
from celery.task.base import Task
# For recent versions, Task has been moved to celery.task.app:
# from celery.app.task import Task
# See http://docs.celeryproject.org/en/latest/reference/celery.app.task.html

class CeleryTestCaseBase(TestCase):

    def setUp(self):
        super(CeleryTestCaseBase, self).setUp()
        self.applied_tasks = []

        self.task_apply_async_orig = Task.apply_async

        @classmethod
        def new_apply_async(task_class, args=None, kwargs=None, **options):
            self.handle_apply_async(task_class, args, kwargs, **options)

        # monkey patch the regular apply_sync with our method
        Task.apply_async = new_apply_async

    def tearDown(self):
        super(CeleryTestCaseBase, self).tearDown()

        # Reset the monkey patch to the original method
        Task.apply_async = self.task_apply_async_orig

    def handle_apply_async(self, task_class, args=None, kwargs=None, **options):
        self.applied_tasks.append((task_class, tuple(args), kwargs))

    def assert_task_sent(self, task_class, *args, **kwargs):
        was_sent = any(task_class == task[0] and args == task[1] and kwargs == task[2]
                       for task in self.applied_tasks)
        self.assertTrue(was_sent, 'Task not called w/class %s and args %s' % (task_class, args))

    def assert_task_not_sent(self, task_class):
        was_sent = any(task_class == task[0] for task in self.applied_tasks)
        self.assertFalse(was_sent, 'Task was not expected to be called, but was.  Applied tasks: %s' %                 self.applied_tasks)

这是一个在测试用例中如何使用它的示例:

mymodule.py

from my_tasks import SomeTask

def run_some_task(should_run):
    if should_run:
        SomeTask.delay(1, some_kwarg=2)

test_mymodule.py

class RunSomeTaskTest(CeleryTestCaseBase):
    def test_should_run(self):
        run_some_task(should_run=True)
        self.assert_task_sent(SomeTask, 1, some_kwarg=2)

    def test_should_not_run(self):
        run_some_task(should_run=False)
        self.assert_task_not_sent(SomeTask)

4

由于我仍然会在搜索结果中看到此信息,因此设置会被覆盖

TEST_RUNNER = 'djcelery.contrib.test_runner.CeleryTestSuiteRunner'

根据Celery Docs为我工作


1

这就是我所做的

在myapp.tasks.py中,我具有:

from celery import shared_task

@shared_task()
def add(a, b):
    return a + b

在myapp.test_tasks.py中,我具有:

from django.test import TestCase, override_settings
from myapp.tasks import add


class TasksTestCase(TestCase):

    def setUp(self):
        ...

    @override_settings(CELERY_TASK_ALWAYS_EAGER=True,CELERY_TASK_EAGER_PROPOGATES=True)
    def test_create_sections(self):
        result= add.delay(1,2)
        assert result.successful() == True
        assert result.get() == 3

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.