我知道这会被视为重复,但我已经在问这个问题之前环顾四周,但所有问题似乎都已过时或根本没有帮助我的问题。这是我在写这个问题之前所看到的:
我目前正在开发一个大量使用Celery来处理异步任务的项目;使整个代码库稳定我为整个项目编写单元测试但是到目前为止我还没有能够为Celery编写单个工作测试。
我的大多数代码都需要跟踪所运行的任务,以确定是否已准备好查询所有结果。这在我的代码中实现如下:
@app.task(bind=True)
def some_task(self, record_id):
associate(self.request.id, record_id) # Not the actual DB code, but you get the idea
# Somewhere else in my code, eg: Flask endpoint
record = some_db_record()
some_task.apply_async(args=[record.id])
由于我没有基于* nix的计算机来运行我的代码,我尝试通过将always eager选项设置为true来解决此问题,但是这会在任何子任务尝试查询结果时导致问题:
@app.task(bind=True)
def foo(self):
task = bar.apply_async()
foo_poll.apply_async(args=[task.id])
@app.task(bind=True, max_retries=None):
def foo_poll(self, celery_id)
task = AsyncResult(celery_id)
if not task.ready(): # RuntimeError: Cannot retrieve result with task_always_eager enabled
return self.retry(countdown=5)
else:
pass # Do something with the result
@app.task
def bar():
time.sleep(10)
我尝试通过修补AsyncResult
方法来修复此问题,但这会导致问题,self.request.id
将是None
:
with patch.object(AsyncResult, "_get_task_meta", side_effect=lambda: {"status": SUCCESS, "result": None}) as method:
foo()
@app.task(bind=True)
def foo(self):
pass # self.request.id is now None, which I need to track sub-tasks
有谁知道我怎么做到这一点?或者,如果Celery甚至值得使用?我发现文档以及与测试有关的任何问题都非常复杂,我只是想放弃它,然后再回到多线程。
答案 0 :(得分:3)
我遇到了同样的问题,并提出了两种可能的方法:
if self.request.called_directly
的交互并运行任务
如果为True则直接使用,如果为False,则为apply_async
。task.ready()
和其他状态会检查我检查ALWAYS_EAGER
和任务准备情况的功能。最终我想出了两种规则的混合,以尽可能避免嵌套任务。并且尽可能在@app.task
中放置尽可能少的代码,以便能够尽可能多地隔离测试任务函数。
看起来可能非常令人沮丧和糟糕,但实际上并非如此。
你也可以查看像Sentry这样的大家伙这样做(剧透:嘲笑和一些漂亮的助手)。
所以它绝对可能,它不是一个简单的方法来找到一些最佳实践。
答案 1 :(得分:2)
可以通过直接调用函数并使用模拟替换任务对象来测试没有celery任务绑定的功能。
内部功能隐藏在some_task.__wrapped__.__func__
后面。
以下是在测试案例中如何使用它的示例:
def test_some_task(self):
mock_task = Mock()
mock_task.request.id = 5 # your test data here
record_id = 5 # more test data
some_task_inner = some_task.__wrapped__.__func__
some_task_inner(mock_task, record_id)
# ...
答案 2 :(得分:0)
我还没有使用芹菜一段时间,除非事情发生变化,否则你应该能够直接调用你的方法来锻炼它们作为单元测试。
@app.task(bind=True, max_retries=None):
def foo_poll(self, celery_id)
task = AsyncResult(celery_id)
if not task.ready(): # RuntimeError: Cannot retrieve result with task_always_eager enabled
return self.retry(countdown=5)
else:
pass # Do something with the result
对于您的单元测试,您可以:
AsyncResult
,触发正确的分支retry
方法,并将其称为当然,这只是锻炼你方法的逻辑,而不是芹菜。我通常会进行一个或两个集成(协作)测试,指定ALWAYS_EAGER以便它通过芹菜代码,即使芹菜将在没有队列的内存中执行。