Тестирование с Celery
Тестирование с Celery делится на две части:
Модульные и интеграционные тесты: с использованием
celery.contrib.pytest.Smoke-тестирование / тестирование в production: с использованием pytest-celery >= 1.0.0
Установка плагина pytest-celery также установит инфраструктуру celery.contrib.pytest наряду с инфраструктурой плагина pytest. Разница заключается в том, как вы их используете.
Предупреждение
Оба API НЕ совместимы друг с другом. Плагин pytest-celery основан на Docker, а celery.contrib.pytest — на mock-объектах.
Чтобы использовать инфраструктуру celery.contrib.pytest, следуйте приведённым ниже инструкциям.
У плагина pytest-celery есть собственная документация.
Задачи и модульные тесты
Для тестирования поведения задач в модульных тестах предпочтительно использовать mock-объекты.
Режим eager
Режим eager, включаемый параметром task_always_eager, по определению не подходит для модульных тестов.
При тестировании в режиме eager вы проверяете только эмуляцию происходящего в worker, а между эмуляцией и тем, что происходит в действительности, есть множество расхождений.
Обратите внимание, что задачи, выполненные в режиме eager, по умолчанию не записывают результаты в backend. Если вы хотите включить эту функцию, см. task_store_eager_result.
Задача Celery во многом похожа на представление веб-приложения: она должна лишь определять, как выполнить действие в контексте вызова в качестве задачи.
В идеале это означает, что задачи отвечают только за такие вещи, как сериализация, заголовки сообщений, повторы и тому подобное, а фактическая логика реализуется в другом месте.
Предположим, у нас есть такая задача:
from .models import Product
@app.task(bind=True)
def send_order(self, product_pk, quantity, price):
price = Decimal(price) # json serializes this to string.
# models are passed by id, not serialized.
product = Product.objects.get(product_pk)
try:
product.order(quantity, price)
except OperationalError as exc:
raise self.retry(exc=exc)
Note: Если задача является связанной, первым аргументом задачи всегда будет экземпляр самой задачи (self). Это означает, что первым аргументом будет self, и вы сможете использовать методы и атрибуты класса Task.
Для этой задачи можно написать модульные тесты с использованием mock-объектов, как в этом примере:
from pytest import raises
from celery.exceptions import Retry
# for python 2: use mock.patch from `pip install mock`.
from unittest.mock import patch
from proj.models import Product
from proj.tasks import send_order
class test_send_order:
@patch('proj.tasks.Product.order') # < patching Product in module above
def test_success(self, product_order):
product = Product.objects.create(
name='Foo',
)
send_order(product.pk, 3, Decimal(30.3))
product_order.assert_called_with(3, Decimal(30.3))
@patch('proj.tasks.Product.order')
@patch('proj.tasks.send_order.retry')
def test_failure(self, send_order_retry, product_order):
product = Product.objects.create(
name='Foo',
)
# Set a side effect on the patched methods
# so that they raise the errors we want.
send_order_retry.side_effect = Retry()
product_order.side_effect = OperationalError()
with raises(Retry):
send_order(product.pk, 3, Decimal(30.6))
pytest
Добавлено в версии 4.0.
Celery также предоставляет плагин для https://pypi.org/project/pytest/, который добавляет фикстуры для интеграционных (или модульных) тестов.
Включение
Изначально плагин поставляется в Celery в отключённом состоянии. Чтобы включить его, можно:
pip install celery[pytest]или добавить переменную окружения
PYTEST_PLUGINS=celery.contrib.pytestили добавить
pytest_plugins = ("celery.contrib.pytest", )в корневой файл conftest.py
Метки
celery — настройка тестовой конфигурации приложения.
Метка celery позволяет переопределить конфигурацию для отдельного тестового случая:
@pytest.mark.celery(result_backend='redis://')
def test_something():
...
или для всех тестовых случаев в классе:
@pytest.mark.celery(result_backend='redis://')
class test_something:
def test_one(self):
...
def test_two(self):
...
Фикстуры
Область действия функции
celery_app — приложение Celery для тестирования.
Эта фикстура возвращает приложение Celery, которое можно использовать для тестирования.
Пример:
def test_create_task(celery_app, celery_worker):
@celery_app.task
def mul(x, y):
return x * y
celery_worker.reload()
assert mul.delay(4, 4).get(timeout=10) == 16
celery_worker — встроенный работающий worker.
Эта фикстура запускает экземпляр worker Celery, который можно использовать для интеграционных тестов. Worker запускается в отдельном потоке и останавливается сразу после завершения теста.
По умолчанию фикстура ждёт до 10 секунд, пока worker выполнит оставшиеся задачи, и вызывает исключение, если время ожидания превышено. Тайм-аут можно настроить, задав ключ shutdown_timeout в словаре, возвращаемом фикстурой celery_worker_parameters().
Пример:
# Put this in your conftest.py
@pytest.fixture(scope='session')
def celery_config():
return {
'broker_url': 'amqp://',
'result_backend': 'redis://'
}
def test_add(celery_worker):
mytask.delay()
# If you wish to override some setting in one test cases
# only - you can use the ``celery`` mark:
@pytest.mark.celery(result_backend='rpc')
def test_other(celery_worker):
...
По умолчанию heartbeat отключены, то есть тестовый worker не отправляет события для worker-online, worker-offline и worker-heartbeat. Чтобы включить heartbeat, измените фикстуру celery_worker_parameters():
# Put this in your conftest.py
@pytest.fixture(scope="session")
def celery_worker_parameters():
return {"without_heartbeat": False}
...
Область действия сеанса
celery_config — переопределение конфигурации тестового приложения Celery.
Эту фикстуру можно переопределить, чтобы настроить тестовое приложение Celery.
Конфигурация, возвращаемая фикстурой, будет использоваться для настройки фикстур celery_app() и celery_session_app().
Пример:
@pytest.fixture(scope='session')
def celery_config():
return {
'broker_url': 'amqp://',
'result_backend': 'rpc',
}
celery_parameters — переопределение параметров тестового приложения Celery.
Эту фикстуру можно переопределить, чтобы изменить параметры __init__ тестового приложения Celery. В отличие от celery_config(), они напрямую передаются при создании экземпляра Celery.
Конфигурация, возвращаемая фикстурой, будет использоваться для настройки фикстур celery_app() и celery_session_app().
Пример:
@pytest.fixture(scope='session')
def celery_parameters():
return {
'task_cls': my.package.MyCustomTaskClass,
'strict_typing': False,
}
celery_worker_parameters — переопределение параметров worker Celery.
Эту фикстуру можно переопределить, чтобы изменить параметры __init__ тестовых worker Celery. Они напрямую передаются в WorkController при его создании.
Конфигурация, возвращаемая фикстурой, будет использоваться для настройки фикстур celery_worker() и celery_session_worker().
Пример:
@pytest.fixture(scope='session')
def celery_worker_parameters():
return {
'queues': ('high-prio', 'low-prio'),
'exclude_queues': ('celery'),
}
celery_enable_logging — переопределение для включения журналирования во встроенных worker.
Эту фикстуру можно переопределить, чтобы включить журналирование во встроенных worker.
Пример:
@pytest.fixture(scope='session')
def celery_enable_logging():
return True
celery_includes — добавление дополнительных импортов для встроенных worker.
Эту фикстуру можно переопределить, чтобы включать модули при запуске встроенного worker.
Можно вернуть список имён модулей для импорта: это могут быть модули задач, модули, регистрирующие сигналы, и так далее.
Пример:
@pytest.fixture(scope='session')
def celery_includes():
return [
'proj.tests.tasks',
'proj.tests.celery_signal_handlers',
]
celery_worker_pool — переопределение пула для встроенных worker.
Эту фикстуру можно переопределить, чтобы настроить пул выполнения для встроенных worker.
Пример:
@pytest.fixture(scope='session')
def celery_worker_pool():
return 'prefork'
Предупреждение
Пулы gevent/eventlet использовать нельзя, если только весь набор тестов не запускается с включёнными monkeypatch.
celery_session_worker — встроенный worker, работающий на протяжении всего сеанса.
Эта фикстура запускает worker, который работает на протяжении всего сеанса тестирования (он не будет запускаться и останавливаться для каждого теста).
Пример:
# Add this to your conftest.py
@pytest.fixture(scope='session')
def celery_config():
return {
'broker_url': 'amqp://',
'result_backend': 'rpc',
}
# Do this in your tests.
def test_add_task(celery_session_worker):
assert add.delay(2, 2).get() == 4
Предупреждение
Вероятно, смешивать worker, работающие весь сеанс, и временные worker — плохая идея…
celery_session_app — приложение Celery для тестирования (область действия сеанса).
Эту фикстуру можно использовать в других фикстурах с областью действия сеанса, которым требуется ссылка на экземпляр приложения Celery.
use_celery_app_trap — вызов исключения при переходе к приложению по умолчанию.
Эту фикстуру можно переопределить в conftest.py, чтобы включить «ловушку приложения»: если что-либо попытается обратиться к приложению по умолчанию или current_app, будет вызвано исключение.
Пример:
@pytest.fixture(scope='session')
def use_celery_app_trap():
return True
Если тесту необходимо обратиться к приложению по умолчанию, его нужно пометить с помощью фикстуры depends_on_current_app:
@pytest.mark.usefixtures('depends_on_current_app')
def test_something():
something()
Copyright © 2017-2026 Asif Saif Uddin, core team & contributors. All rights reserved.
Celery is licensed under The BSD License (3 Clause, also known as the new BSD license). The license is an OSI approved Open Source license and is GPL-compatible.
https://docs.celeryq.dev/en/stable/userguide/testing.html