Юніт-тестування за допомогою django-селери?


82

Я намагаюся запропонувати методологію тестування для нашого проекту django-celery . Я прочитав примітки в документації , але це не дало мені хорошого уявлення про те, що насправді робити. Мене не турбує тестування завдань на власне демонах, а лише функціональність мого коду. В основному мені цікаво:

  1. Як ми можемо обходити task.delay()під час тесту (я спробував налаштування, CELERY_ALWAYS_EAGER = Trueале це не мало значення)?
  2. Як ми використовуємо рекомендовані тестові налаштування (якщо це найкращий спосіб), фактично не змінюючи наші settings.py?
  3. Чи можемо ми все-таки використовувати manage.py testабо ми повинні використовувати власний бігун?

Загалом, будь-які підказки або поради щодо тестування з селерою були б дуже корисними.


1
що ти маєш на увазі CELERY_ALWAYS_EAGERне має значення?
askol

Я все ще отримую помилки про те, що не можу зв’язатися з rabbitmq.
Джейсон Вебб,

У вас є відстеження? Думаю, щось інше, ніж .delayспроби встановити зв’язок.
askol

11
BROKER_BACKEND=memoryВ цьому випадку налаштування може допомогти.
askol

Запитайте, що ви мали рацію. BROKER_BACKEND=memoryце виправили. Якщо ви поставите це як відповідь, я позначу це правильним.
Джейсон Вебб,

Відповіді:



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

Якщо ви хочете застосувати це до всіх тестів, ви можете використати білок для селери, як описано на http://docs.celeryproject.org/en/2.5/django/unit-testing.html, який в основному встановлює ці самі налаштування, за винятком (BROKER_BACKEND = 'memory' ).

У налаштуваннях:

TEST_RUNNER = 'djcelery.contrib.test_runner.CeleryTestSuiteRunner'

Подивіться на джерело CeleryTestSuiteRunner, і цілком зрозуміло, що відбувається.


1
Це не спрацювало з селерою 4, навіть з перейменуванням полів звідси
Шаді

Працює над селерою 3.1. У мене просто є тести Celery, які успадковуються від батьківського класу за допомогою цього декоратора. Таким чином, це потрібно лише в одному місці, і не потрібно втягувати djcelery.
kontextify

1
Це чудово працює на Celery 4.4. та Django 2.2. Найкращий підхід для запуску модульних тестів, з яким я стикався до цього часу.
Ерік Калкокен

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



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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.