簡體   English   中英

直接對 celery 任務進行單元測試

[英]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)
    # ...

我遇到了同樣的問題並提出了兩種可能的方法:

  1. 直接在測試中調用任務,並使用if self.request.called_directly包裝所有內部 celery 交互,如果為 True 則直接運行任務,如果為 False 則使用apply_async
  2. 使用我檢查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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM