셀러리 작업을 어떻게 단위 테스트합니까?


답변:


61

모든 unittest lib를 사용하여 동기식으로 작업을 테스트 할 수 있습니다. 나는 일반적으로 셀러리 작업을 할 때 두 가지 다른 테스트 세션을 수행합니다. 첫 번째는 (내가 제안한 것처럼) 완전히 동 기적이며 알고리즘이해야 할 일을 수행하는지 확인하는 것이어야합니다. 두 번째 세션은 전체 시스템 (브로커 포함)을 사용하고 직렬화 문제 나 기타 배포, 통신 문제가 없는지 확인합니다.

그래서:

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)

도움이 되었기를 바랍니다.


1
- HttpDispatchTask 사용하는 작업을 제외하고 그 작품 docs.celeryproject.org/en/latest/userguide/remote-tasks.html을 설정 celery.conf.CELERY_ALWAYS_EAGER = True로하지만, 심지어 또한 설정 celery.conf.CELERY_IMPORTS와 내가 가지고 = ( 'celery.task.http') NotRegistered로 테스트 실패 : celery.task.http.HttpDispatchTask
davidmytton

이상합니다. 수입 문제가없는 것이 확실합니까? 이 테스트 는 효과가 있습니다 (셀러리가 기대하는 것을 반환하도록 응답을 속이기 때문입니다). 또한 CELERY_IMPORTS에 정의 된 모듈은 워커 초기화 중에 임포트되므로 이를 방지하기 위해을 호출하는 것이 좋습니다 celery.loader.import_default_modules().
FlaPer87

나는 또한 당신이 여기를 살펴볼 것을 제안 할 것 입니다. http 요청을 조롱합니다. Dunno가 도움이되는지 알고 있습니다. 실행중인 서비스를 테스트하고 싶습니까?
FlaPer87

52

나는 이것을 사용한다 :

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를 사용하면 작업을 동기식으로 실행할 수 있으며 셀러리 서버가 필요하지 않습니다.


1
나는 이것이 구식이라고 생각합니다 ImportError: No module named celeryconfig.
Daenyth

7
나는 위의 모듈 celeryconfig.py이 자신의 패키지에 존재 한다고 가정합니다 . docs.celeryproject.org/en/latest/getting-started/…를 참조하십시오 .
Kamil Sindi

1
나는 그것이 오래되었다는 것을 알고 있지만 수업 add내에서 OP의 질문에서 작업을 시작하는 방법에 대한 전체 예제를 제공 할 수 TestCase있습니까?
Kruupös

@ MaxChrétien 죄송합니다. 더 이상 셀러리를 사용하지 않기 때문에 전체 예제를 제공 할 수 없습니다. 평판 포인트가 충분하면 내 질문을 편집 할 수 있습니다. 충분하지 않은 경우이 답변에 복사하여 붙여 넣을 내용을 알려주십시오.
guettli

1
@ miken32 감사합니다. 가장 최근의 답변은 내가 돕고 싶은 문제를 어떻게 든 다루기 때문에 4.0 공식 문서 CELERY_TASK_ALWAYS_EAGER가 단위 테스트에 사용하는 것을 권장하지 않는다는 의견을 남겼습니다 .
krassowski

33

정확히 무엇을 테스트하고 싶은지에 따라 다릅니다.

  • 태스크 코드를 직접 테스트하십시오. "task.delay (...)"를 호출하지 말고 단위 테스트에서 "task (...)"를 호출하십시오.
  • CELERY_ALWAYS_EAGER를 사용하십시오 . 이렇게하면 "task.delay (...)"라고 말하는 지점에서 작업이 즉시 호출되므로 전체 경로를 테스트 할 수 있습니다 (비동기 동작은 아님).

24

unittest

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

Celery 4 사용자의 경우 :

@override_settings(CELERY_TASK_ALWAYS_EAGER=True)

설정 이름이 변경되었으며 업그레이드를 선택한 경우 업데이트가 필요하므로

https://docs.celeryproject.org/en/latest/history/whatsnew-4.0.html?highlight=what%20is%20new#lowercase-setting-names


11
공식 문서 에 따르면 "task_always_eager"(이전 "CELERY_ALWAYS_EAGER")의 사용은 단위 테스트에 적합하지 않습니다. 대신 그들은 Celery 앱을 단위 테스트하는 다른 훌륭한 방법을 제안합니다.
krassowski

4
단위 테스트에서 eager task를 원하지 않는 이유는 테스트를하지 않기 때문입니다. 예를 들어 프로덕션에서 코드를 사용하면 발생하는 매개 변수의 직렬화.
damd

15

현재 셀러리 3.0 , 설정하는 하나의 방법 CELERY_ALWAYS_EAGER으로 장고 입니다 :

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())

7

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를 사용할 필요가 없습니다.


반대 투표자님께, 왜 이것이 잘못된 대답인지 공유 하시겠습니까? 진심으로 감사합니다.
alanjds

2
나를 위해 작동하지 않았고 테스트 스위트가 중단되었습니다. 더 많은 컨텍스트를 제공 할 수 있습니까? (아직 투표하지 않았습니다;)).
duality_

제 경우에는 메모리 브로커와 cache + memory 백엔드를 사용하도록 celey_config 픽스쳐를 명시 적으로 설정해야했습니다
sanzoghenzo

5

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

2

제 경우에는 (그리고 다른 많은 사람들을 가정합니다), 제가 원했던 것은 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데코레이터는 셀러리 내부 로직을 많이 수행하기 때문에 실제로는 단위 테스트가 아닙니다.

그래서 저에게는 두 가지 옵션이 있습니다.

옵션 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'

이 솔루션은 훨씬 더 수동적이지만 셀러리 범위를 잃지 않고 반복하지 않고 실제로 단위 테스트에 필요한 제어 기능을 제공합니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.