La documentation de Celery mentionne le test de Celery dans Django mais n'explique pas comment tester une tâche Celery si vous n'utilisez pas Django. Comment est-ce que tu fais ça?
La documentation de Celery mentionne le test de Celery dans Django mais n'explique pas comment tester une tâche Celery si vous n'utilisez pas Django. Comment est-ce que tu fais ça?
Réponses:
Il est possible de tester des tâches de manière synchrone en utilisant n'importe quelle bibliothèque unittest disponible. Je fais normalement 2 sessions de test différentes lorsque je travaille avec des tâches de céleri. Le premier (comme je le suggère ci-dessous) est complètement synchrone et devrait être celui qui garantit que l'algorithme fait ce qu'il doit faire. La deuxième session utilise tout le système (y compris le courtier) et s'assure que je n'ai pas de problèmes de sérialisation ou de tout autre problème de distribution ou de communication.
Alors:
from celery import Celery
celery = Celery()
@celery.task
def add(x, y):
return x + y
Et votre test:
from nose.tools import eq_
def test_add_task():
rst = add.apply(args=(4, 4)).get()
eq_(rst, 8)
J'espère que cela pourra aider!
celery.loader.import_default_modules()
.
J'utilise ceci:
with mock.patch('celeryconfig.CELERY_ALWAYS_EAGER', True, create=True):
...
Documents: http://docs.celleryproject.org/en/3.1/configuration.html#celery-always-eager
CELERY_ALWAYS_EAGER vous permet d'exécuter votre tâche de manière synchrone, et vous n'avez pas besoin d'un serveur de céleri.
ImportError: No module named celeryconfig
.
celeryconfig.py
existe dans son package. Voir docs.celleryproject.org/en/latest/getting-started/… .
add
partir de la question d'OP au sein d'une TestCase
classe?
CELERY_TASK_ALWAYS_EAGER
pour les tests unitaires.
Cela dépend exactement de ce que vous voulez tester.
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
Pour ceux sur Celery 4 c'est:
@override_settings(CELERY_TASK_ALWAYS_EAGER=True)
Étant donné que les noms des paramètres ont été modifiés et doivent être mis à jour si vous choisissez de mettre à niveau, consultez
Depuis Celery 3.0 , une façon de définir CELERY_ALWAYS_EAGER
dans Django est:
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())
Depuis Celery v4.0 , les appareils py.test sont fournis pour démarrer un ouvrier céleri juste pour le test et sont arrêtés une fois terminé:
def test_myfunc_is_executed(celery_session_worker):
# celery_session_worker: <Worker: gen93553@gnpill.local (running)>
assert myfunc.delay().wait(3)
Parmi les autres appareils décrits sur http://docs.celleryproject.org/en/latest/userguide/testing.html#py-test , vous pouvez modifier les options par défaut du céleri en redéfinissant l' celery_config
appareil de cette façon:
@pytest.fixture(scope='session')
def celery_config():
return {
'accept_content': ['json', 'pickle'],
'result_serializer': 'pickle',
}
Par défaut, le test worker utilise un courtier en mémoire et un backend de résultats. Pas besoin d'utiliser un Redis ou RabbitMQ local si vous ne testez pas des fonctionnalités spécifiques.
référence utilisant pytest.
def test_add(celery_worker):
mytask.delay()
si vous utilisez flask, définissez la configuration de l'application
CELERY_BROKER_URL = 'memory://'
CELERY_RESULT_BACKEND = 'cache+memory://'
et en 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
Dans mon cas (et je suppose que beaucoup d'autres), tout ce que je voulais, c'était tester la logique interne d'une tâche en utilisant pytest.
TL, DR; a fini par se moquer de tout ( OPTION 2 )
Exemple de cas d'utilisation :
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'
mais, puisque le shared_task
décorateur fait beaucoup de logique interne au céleri, ce n'est pas vraiment un test unitaire.
Donc, pour moi, il y avait 2 options:
OPTION 1: Logique interne séparée
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);
Cela semble très étrange, et à part le rendre moins lisible, cela nécessite d'extraire et de transmettre manuellement les attributs qui font partie de la requête, par exemple task_id
au cas où vous en auriez besoin, ce qui rend la logique moins pure.
OPTION 2: se
moque de se moquer des internes de céleri
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()
ce qui me permet ensuite de me moquer de l'objet de la requête (encore une fois, au cas où vous auriez besoin d'éléments de la requête, comme l'id ou le compteur de tentatives.
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'
Cette solution est beaucoup plus manuelle, mais elle me donne le contrôle dont j'ai besoin pour effectuer un test unitaire , sans me répéter, et sans perdre la lunette céleri.