Celeryのドキュメント Django内でのCeleryのテストに関する記述 。ただし、Djangoを使用していない場合のCeleryタスクのテスト方法については説明していません。これどうやってやるの?
任意のunittest libを使用して、タスクを同期的にテストすることができます。私は通常、セロリのタスクを扱うときに2つの異なるテストセッションを行います。最初の方法(以下でお勧めするように)は完全に同期的であり、アルゴリズムが行うべきことをアルゴリズムが確実に実行するようにする必要があります。 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)
お役に立てば幸いです!
私はこれを使用します:
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を使用すると、タスクを同期的に実行でき、セロリサーバーは不要です。
テストする内容に依存します。
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
Celery 4のユーザーの場合:
@override_settings(CELERY_TASK_ALWAYS_EAGER=True)
設定名が変更されており、アップグレードを選択した場合は更新する必要があるため、
http://docs.celeryproject.org/en/latest/whatsnew-4.0.html#lowercase-setting-names
Celery 3.の時点で、CELERY_ALWAYS_EAGER
in 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())
セロリv4.0であるため、py.testフィクスチャは 提供 であり、テストのためだけにセロリワーカーを起動してシャットダウンします。それが終わったら:
def test_myfunc_is_executed(celery_session_worker):
# celery_session_worker: <Worker: [email protected] (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を使用する必要はありません。
私の場合(そして他の多くの人も想定しています)、pytestを使用してタスクの内部ロジックをテストするだけでした。
TL; DR;最終的にすべてをあざけりました(OPTION 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()
これにより、リクエストオブジェクトをモックできます(ID、リトライカウンターなど、リクエストから何かが必要になった場合に備えて)。
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'
このソリューションははるかに手作業ですが、実際に繰り返す必要がなく、セロリのスコープを失うことなく、実際にnitテストする必要があるコントロールを提供します。