Làm thế nào để bạn kiểm tra đơn vị một nhiệm vụ Cần tây?


Câu trả lời:


61

Có thể kiểm tra đồng bộ các tác vụ bằng cách sử dụng bất kỳ lib nào mới nhất hiện có. Tôi bình thường thực hiện 2 phiên kiểm tra khác nhau khi làm việc với các tác vụ cần tây. Cái đầu tiên (như tôi đang đề xuất ở bên dưới) là hoàn toàn đồng bộ và phải là cái đảm bảo thuật toán thực hiện những gì nó phải làm. Phiên thứ hai sử dụng toàn bộ hệ thống (bao gồm cả nhà môi giới) và đảm bảo rằng tôi không gặp vấn đề tuần tự hóa hoặc bất kỳ vấn đề phân phối, giao tiếp nào khác.

Vì thế:

from celery import Celery

celery = Celery()

@celery.task
def add(x, y):
    return x + y

Và bài kiểm tra của bạn:

from nose.tools import eq_

def test_add_task():
    rst = add.apply(args=(4, 4)).get()
    eq_(rst, 8)

Hy vọng rằng sẽ giúp!


1
Điều đó hoạt động ngoại trừ các tác vụ sử dụng HttpDispatchTask - docs.celeryproject.org/en/latest/userguide/remote-tasks.html trong đó tôi phải đặt celery.conf.CELERY_ALWAYS_EAGER = Đúng nhưng ngay cả khi đặt celery.conf.CELERY_IMPORTS = ( 'celery.task.http') thử nghiệm thất bại với NotRegistered: celery.task.http.HttpDispatchTask
davidmytton

Thật kỳ lạ, bạn có chắc mình không gặp một số vấn đề về nhập không? Thử nghiệm này hoạt động (lưu ý rằng tôi đang đánh lừa phản hồi để nó trả về những gì cần tây mong đợi). Ngoài ra, các mô-đun được xác định trong CELERY_IMPORTS sẽ được nhập trong quá trình khởi tạo worker , để tránh điều này, tôi khuyên bạn nên gọi celery.loader.import_default_modules().
FlaPer87

Tôi cũng sẽ đề nghị bạn xem ở đây . Nó chế nhạo yêu cầu http. Không biết nó có giúp ích được gì không, tôi đoán là bạn muốn thử nghiệm một dịch vụ đang hoạt động, phải không?
FlaPer87

52

Tôi sử dụng cái này:

with mock.patch('celeryconfig.CELERY_ALWAYS_EAGER', True, create=True):
    ...

Tài liệu: http://docs.celeryproject.org/en/3.1/configuration.html#celery-always-eager

CELERY_ALWAYS_EAGER cho phép bạn chạy đồng bộ tác vụ của mình và bạn không cần máy chủ cần tây.


1
Tôi nghĩ điều này đã lỗi thời - tôi hiểu ImportError: No module named celeryconfig.
Daenyth

7
Tôi tin rằng ở trên giả sử mô-đun celeryconfig.pytồn tại trong gói của một người. Xem docs.celeryproject.org/en/latest/getting-started/… .
Kamil Sindi

1
Tôi biết nó cũ nhưng bạn có thể cung cấp một ví dụ đầy đủ về cách khởi chạy các tác vụ addtừ câu hỏi của OP trong một TestCaselớp không?
Kruupös

@ MaxChrétien xin lỗi, tôi không thể cung cấp một ví dụ đầy đủ vì tôi không sử dụng cần tây nữa. Bạn có thể chỉnh sửa câu hỏi của tôi, nếu bạn có đủ điểm danh tiếng. Nếu bạn không có đủ, vui lòng cho tôi biết những gì tôi nên sao chép + dán vào câu trả lời này.
guettli

1
@ miken32 xin cảm ơn. Vì câu trả lời gần đây nhất bằng cách nào đó giải quyết vấn đề mà tôi muốn trợ giúp, tôi chỉ để lại nhận xét rằng các tài liệu chính thức cho 4.0 không khuyến khích sử dụng CELERY_TASK_ALWAYS_EAGERcho thử nghiệm đơn vị.
krassowski

33

Phụ thuộc vào chính xác những gì bạn muốn thử nghiệm.

  • Kiểm tra mã tác vụ trực tiếp. Không gọi "task.delay (...)" mà chỉ gọi "task (...)" từ các bài kiểm tra đơn vị của bạn.
  • Sử dụng CELERY_ALWAYS_EAGER . Điều này sẽ khiến các nhiệm vụ của bạn được gọi ngay lập tức tại thời điểm bạn nói "task.delay (...)", vì vậy bạn có thể kiểm tra toàn bộ đường dẫn (nhưng không phải bất kỳ hành vi không đồng bộ nào).

24

độc nhất

import unittest

from myproject.myapp import celeryapp

class TestMyCeleryWorker(unittest.TestCase):

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

đồ đạc 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):
    ...

Phụ lục: làm cho sự tôn trọng send_task háo hức

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

Đối với những người trên Celery 4 đó là:

@override_settings(CELERY_TASK_ALWAYS_EAGER=True)

Vì tên cài đặt đã được thay đổi và cần cập nhật nếu bạn chọn nâng cấp, hãy xem

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


11
Theo tài liệu chính thức , việc sử dụng "task_always_eager" (trước đó là "CELERY_ALWAYS_EAGER") không thích hợp cho thử nghiệm đơn vị. Thay vào đó, họ đề xuất một số cách tuyệt vời khác để kiểm tra đơn vị ứng dụng Cần tây của bạn.
krassowski

4
Tôi chỉ nói thêm rằng lý do tại sao bạn không muốn các nhiệm vụ háo hức trong các bài kiểm tra đơn vị của mình là vì khi đó bạn không kiểm tra, ví dụ: việc tuần tự hóa các thông số sẽ xảy ra khi bạn đang sử dụng mã trong sản xuất.
chết tiệt

15

Kể từ Celery 3.0 , một cách để thiết lập CELERY_ALWAYS_EAGERtrong Django là:

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

Kể từ Celery v4.0 , đồ đạc py.test được cung cấp để bắt đầu một công nhân cần tây chỉ để thử nghiệm và sẽ tắt khi hoàn thành:

def test_myfunc_is_executed(celery_session_worker):
    # celery_session_worker: <Worker: gen93553@gnpill.local (running)>
    assert myfunc.delay().wait(3)

Trong số các đồ đạc khác được mô tả trên http://docs.celeryproject.org/en/latest/userguide/testing.html#py-test , bạn có thể thay đổi các tùy chọn mặc định của cần tây bằng cách xác định lại đồ dùng celery_configtheo cách này:

@pytest.fixture(scope='session')
def celery_config():
    return {
        'accept_content': ['json', 'pickle'],
        'result_serializer': 'pickle',
    }

Theo mặc định, test worker sử dụng một trình môi giới trong bộ nhớ và phụ trợ kết quả. Không cần sử dụng Redis hoặc RabbitMQ cục bộ nếu không thử nghiệm các tính năng cụ thể.


Downvoter thân mến, bạn có muốn chia sẻ tại sao đây là một câu trả lời không tốt? Thật lòng cảm ơn.
alanjds

2
Không hiệu quả với tôi, bộ thử nghiệm chỉ bị treo. Bạn có thể cung cấp thêm một số bối cảnh? (Tôi vẫn chưa bỏ phiếu;)).
lưỡng tính_

Trong trường hợp của tôi, tôi phải đặt cố định celey_config một cách rõ ràng để sử dụng công cụ môi giới bộ nhớ và bộ nhớ đệm + phụ trợ bộ nhớ
sanzoghenzo

5

tham khảo bằng cách sử dụng pytest.

def test_add(celery_worker):
    mytask.delay()

nếu bạn sử dụng flask, hãy đặt cấu hình ứng dụng

    CELERY_BROKER_URL = 'memory://'
    CELERY_RESULT_BACKEND = 'cache+memory://'

và trong 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

Trong trường hợp của tôi (và tôi giả sử nhiều người khác), tất cả những gì tôi muốn là kiểm tra logic bên trong của một nhiệm vụ bằng cách sử dụng pytest.

TL; DR; kết thúc bằng việc chế giễu mọi thứ ( TÙY CHỌN 2 )


Trường hợp sử dụng mẫu :

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'

nhưng kể từ khi shared_task decorator thực hiện rất nhiều logic bên trong cần tây, nên nó không thực sự là một bài kiểm tra đơn vị.

Vì vậy, đối với tôi, có 2 lựa chọn:

TÙY CHỌN 1: Logic nội bộ riêng biệt

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

Điều này trông rất kỳ lạ và ngoài việc làm cho nó khó đọc hơn, nó yêu cầu trích xuất và chuyển các thuộc tính là một phần của yêu cầu theo cách thủ công, ví dụ như task_idtrong trường hợp bạn cần, điều này làm cho logic kém thuần túy hơn.

LỰA CHỌN 2: Chế nhạo chế
giễu nội tạng cần tây

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

sau đó cho phép tôi mô phỏng đối tượng yêu cầu (một lần nữa, trong trường hợp bạn cần những thứ từ yêu cầu, như id hoặc bộ đếm thử lại.

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'

Giải pháp này thủ công hơn nhiều, nhưng nó cung cấp cho tôi sự kiểm soát mà tôi cần để thực sự kiểm tra đơn vị , không lặp lại chính mình và không làm mất phạm vi cần tây.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.