web-dev-qa-db-ja.com

セロリのタスクをどのように単体テストしますか?

Celeryのドキュメント Django内でのCeleryのテストに関する記述 。ただし、Djangoを使用していない場合のCeleryタスクのテスト方法については説明していません。これどうやってやるの?

94
DavidM

任意の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)

お役に立てば幸いです!

52
FlaPer87

私はこれを使用します:

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を使用すると、タスクを同期的に実行でき、セロリサーバーは不要です。

47
guettli

テストする内容に依存します。

  • タスクコードを直接テストします。 「task.delay(...)」を呼び出さないでください。単体テストから「task(...)」を呼び出してください。
  • CELERY_ALWAYS_EAGER を使用します。これにより、「task.delay(...)」と言った時点でタスクがすぐに呼び出されるため、パス全体をテストできます(ただし、非同期動作はテストできません)。
26
slacy

単体テスト

import unittest

from myproject.myapp import celeryapp

class TestMyCeleryWorker(unittest.TestCase):

  def setUp(self):
      celeryapp.conf.update(CELERY_ALWAYS_EAGER=True)

py.testフィクスチャ

# 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):
    ...

補遺:send_taskを熱心に尊重する

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
22
ksindi

Celery 4のユーザーの場合:

@override_settings(CELERY_TASK_ALWAYS_EAGER=True)

設定名が変更されており、アップグレードを選択した場合は更新する必要があるため、

http://docs.celeryproject.org/en/latest/whatsnew-4.0.html#lowercase-setting-names

19
okrutny

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())
13
Aaron Lelevier

セロリ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を使用する必要はありません。

3
alanjds

私の場合(そして他の多くの人も想定しています)、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テストする必要があるコントロールを提供します。

2
Daniel Dubovski