Celeryのドキュメントでは、Django内でのCeleryのテストについて言及していますが、Djangoを使用していない場合のCeleryタスクのテスト方法については説明していません。これどうやってやるの?
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)
お役に立てば幸いです。
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を使用すると、タスクを同期的に実行でき、Celeryサーバーは必要ありません。
ImportError: No module named celeryconfig
。
celeryconfig.py
がパッケージ内にあると想定しています。docs.celeryproject.org/en/latest/getting-started/…を参照してください。
add
ますが、TestCase
クラス内でOPの質問からタスクを起動する方法の完全な例を提供できますか?
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 v4.0以降、py.testフィクスチャは、テストのためだけにceleryワーカーを起動するために提供されており、完了時にシャットダウンされます。
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を使用する必要はありません。
pytestを使用した参照。
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()
これにより、リクエストオブジェクトをモックすることができます(ここでも、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'
このソリューションははるかに手作業ですが、自分自身を繰り返したり、セロリのスコープを失うことなく、実際に単体テストを実行するために必要なコントロールを提供します。