У документації на селеру згадується тестування селери в Django, але не пояснюється, як перевірити завдання із селери, якщо ви не використовуєте Django. Як це зробити?
У документації на селеру згадується тестування селери в Django, але не пояснюється, як перевірити завдання із селери, якщо ви не використовуєте Django. Як це зробити?
Відповіді:
Можна тестувати завдання синхронно, використовуючи будь-яку вкладку одиничних тестів. Я нормально роблю 2 різні тестові сесії, коли працюю із завданнями із селери. Перший (як я пропоную нижче) повністю синхронний і повинен бути таким, який гарантує, що алгоритм робить те, що повинен робити. Другий сеанс використовує всю систему (включаючи брокера) і гарантує, що у мене немає проблем із серіалізацією чи будь-якою іншою проблемою з розподілом, комунікацією.
Так:
from celery import Celery
celery = Celery()
@celery.task
def add(x, y):
return x + y
І ваш тест:
from nose.tools import eq_
def test_add_task():
rst = add.apply(args=(4, 4)).get()
eq_(rst, 8)
Сподіваюся, що це допомагає!
celery.loader.import_default_modules()
.
Я використовую це:
with mock.patch('celeryconfig.CELERY_ALWAYS_EAGER', True, create=True):
...
Документи: http://docs.celeryproject.org/en/3.1/configuration.html#celery-always-eager
CELERY_ALWAYS_EAGER дозволяє виконувати завдання синхронно, і вам не потрібен сервер із селери.
ImportError: No module named celeryconfig
.
celeryconfig.py
існує в пакеті. Дивіться docs.celeryproject.org/en/latest/getting-started/… .
add
з питання ОП в TestCase
класі?
CELERY_TASK_ALWAYS_EAGER
для тестування одиниць.
Залежить від того, що саме ви хочете пройти тестування.
import unittest
from myproject.myapp import celeryapp
class TestMyCeleryWorker(unittest.TestCase):
def setUp(self):
celeryapp.conf.update(CELERY_ALWAYS_EAGER=True)
# conftest.py
from myproject.myapp import celeryapp
@pytest.fixture(scope='module')
def celery_app(request):
celeryapp.conf.update(CELERY_ALWAYS_EAGER=True)
return celeryapp
# test_tasks.py
def test_some_task(celery_app):
...
from celery import current_app
def send_task(name, args=(), kwargs={}, **opts):
# https://github.com/celery/celery/issues/581
task = current_app.tasks[name]
return task.apply(args, kwargs, **opts)
current_app.send_task = send_task
Для тих, хто знаходиться на селері 4, це:
@override_settings(CELERY_TASK_ALWAYS_EAGER=True)
Оскільки назви параметрів були змінені та потребують оновлення, якщо ви вирішили оновити, див
Станом на Celery 3.0 , один із способів встановити CELERY_ALWAYS_EAGER
у Django :
from django.test import TestCase, override_settings
from .foo import foo_celery_task
class MyTest(TestCase):
@override_settings(CELERY_ALWAYS_EAGER=True)
def test_foo(self):
self.assertTrue(foo_celery_task.delay())
Оскільки Celery v4.0 , світильники py.test передбачені для запуску працівника селери лише для тестування, а після завершення роботи відключаються:
def test_myfunc_is_executed(celery_session_worker):
# celery_session_worker: <Worker: gen93553@gnpill.local (running)>
assert myfunc.delay().wait(3)
Серед інших світильників, описаних на http://docs.celeryproject.org/en/latest/userguide/testing.html#py-test , ви можете змінити параметри за замовчуванням для селери, переглянувши celery_config
кріплення таким чином:
@pytest.fixture(scope='session')
def celery_config():
return {
'accept_content': ['json', 'pickle'],
'result_serializer': 'pickle',
}
За замовчуванням тестовий працівник використовує брокер в пам'яті та резервний результат. Не потрібно використовувати локальний Redis або RabbitMQ, якщо не тестувати конкретні функції.
посилання з використанням пістету.
def test_add(celery_worker):
mytask.delay()
якщо ви використовуєте колбу, встановіть конфігурацію програми
CELERY_BROKER_URL = 'memory://'
CELERY_RESULT_BACKEND = 'cache+memory://'
і в conftest.py
@pytest.fixture
def app():
yield app # Your actual Flask application
@pytest.fixture
def celery_app(app):
from celery.contrib.testing import tasks # need it
yield celery_app # Your actual Flask-Celery application
У моєму випадку (і я припускаю багато інших), все, що я хотів, - це перевірити внутрішню логіку завдання за допомогою pytest.
TL; DR; в кінцевому підсумку знущався над усім ( ВАРІАНТ 2 )
Приклад використання :
proj/tasks.py
@shared_task(bind=True)
def add_task(self, a, b):
return a+b;
tests/test_tasks.py
from proj import add_task
def test_add():
assert add_task(1, 2) == 3, '1 + 2 should equal 3'
але, оскільки shared_task
декоратор робить багато внутрішньої логіки селери, це насправді не тестування одиниці.
Отже, для мене було 2 варіанти:
ВАРІАНТ 1: Окрема внутрішня логіка
proj/tasks_logic.py
def internal_add(a, b):
return a + b;
proj/tasks.py
from .tasks_logic import internal_add
@shared_task(bind=True)
def add_task(self, a, b):
return internal_add(a, b);
Це виглядає дуже дивно, і окрім того, що робить його менш читабельним, потрібно вручну витягувати та передавати атрибути, які є частиною запиту, наприклад, task_id
у випадку, якщо вам це потрібно, що робить логіку менш чистою.
ВАРІАНТ 2: знущається з
глузування з внутрішньої частини селери
tests/__init__.py
# noinspection PyUnresolvedReferences
from celery import shared_task
from mock import patch
def mock_signature(**kwargs):
return {}
def mocked_shared_task(*decorator_args, **decorator_kwargs):
def mocked_shared_decorator(func):
func.signature = func.si = func.s = mock_signature
return func
return mocked_shared_decorator
patch('celery.shared_task', mocked_shared_task).start()
що дозволяє мені знущатися над об’єктом запиту (знову ж таки, якщо вам потрібні речі з запиту, наприклад, ідентифікатор або лічильник повторних спроб.
tests/test_tasks.py
from proj import add_task
class MockedRequest:
def __init__(self, id=None):
self.id = id or 1
class MockedTask:
def __init__(self, id=None):
self.request = MockedRequest(id=id)
def test_add():
mocked_task = MockedTask(id=3)
assert add_task(mocked_task, 1, 2) == 3, '1 + 2 should equal 3'
Це рішення набагато більш ручний, але це дає мені контроль Мені потрібно на самому ділі блок тестування, не повторюючи себе, і без втрати обсягу селери.