[英]Unit testing celery tasks directly
我知道這將被視為重復,但在問這個問題之前我已經環顧四周,但是所有問題似乎都已過時或對我的問題根本沒有幫助。 這是我在寫這個問題之前看過的地方:
我目前正在從事一個大量使用 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 是否還值得使用? 我正處於發現文檔和與測試相關的任何問題如此復雜的地步,我只想將它們全部拋棄並回到多線程。
可以通過直接調用並使用 mock 替換任務對象來測試沒有 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)
# ...
我遇到了同樣的問題並提出了兩種可能的方法:
if self.request.called_directly
包裝所有內部 celery 交互,如果為 True 則直接運行任務,如果為 False 則使用apply_async
。ALWAYS_EAGER
和任務准備情況的函數包裝task.ready()
和其他狀態檢查。 最終,我想出了將兩者與規則混合使用的規則,以盡可能避免嵌套任務。 並且盡可能少地在@app.task
中放置代碼,以便能夠盡可能隔離地測試任務功能。
它可能看起來非常令人沮喪和可怕,但實際上並非如此。
您還可以檢查像Sentry這樣的大人物是如何做到這一點的(劇透:模擬和一些漂亮的助手)。
所以這絕對是可能的,但這不是找到一些最佳實踐的簡單方法。
我已經有一段時間沒有使用 celery 了,除非事情發生了變化,否則你應該能夠直接調用你的方法來將它們作為單元測試來執行。
@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 的集成(協作)測試,以便它通過 celery 代碼,即使 celery 將在沒有隊列的情況下在內存中執行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.