【pytest官方文檔】解讀fixtures – 8. yield和addfinalizer的區別(填坑)

上一章中,文末留下了一個坑待填補,疑問是這樣的:

目前從官方文檔中看到的是

We have to be careful though, because pytest will run that finalizer once it』s been added, 
even if that fixture raises an exception after adding the finalizer. 

一旦添加了終結器,pytest便會執行。

但是,當我嘗試在setup程式碼中進行拋錯,終結器的程式碼卻並沒有執行。
嘗試搜索外網暫時也沒得到有效的幫助,只能在GitHub上向pytest提了issue了,這裡算是埋下一個坑,待後續解決。

一、問題回顧

其實說到底還是我理解的不對,可能當時自己處在疑問中難免就會陷入進死循環,後來在github上經過別人提點方才醒悟。
先來看下當時我嘗試演示出上述結果的程式碼,也就是:setup程式碼中進行拋錯,終結器的程式碼卻並沒有執行。

程式碼分為2部分,一個是fixture函數程式碼,另一個則是測試用例。程式碼是不能直接copy出來運行的,是我在項目的用例中
進行改造的,在這裡僅僅幫助說明意思。

# content of conftest.py

@pytest.fixture()
def init_data_allot_task(request):
    query_sql = """
    SELECT id FROM `sm_purchase_allot` WHERE `status`!=5
    """
    db = DB()
    data = db.fetch_one(query_sql)
    db.close()

    def demo_finalizer():
        print("running finalizer code...")
    request.addfinalizer(demo_finalizer)
    return data
# content of testcase
...
def test_allot_detail(init_data_allot_task):
    """

    """
    payload = {
          "allotId": init_data_allot_task[0]
        }
    r = requests.post(QA_URL + API_URL, json=payload, headers=HEADER)
    result = r.json()

    assert result["result"] == "ok"
    assert result["errmsg"] == "success"
    assert len(result["row"]["taskListOfPage"]["resultData"]) > 0

最開始我想做的是,在fixture函數中,讓程式碼db = DB()拋出一個mysql連接超時的錯誤,
然後就能在控制台中看到"running finalizer code..."的輸出。

但是我執行後,並沒有看到預期的輸出,說明setup程式碼拋錯後,addfinalizer程式碼並沒有執行。

最後經過github上朋友指點後,發現還是我自己理解錯了。

二、問題解決

還是來看下官方的原文:

We have to be careful though, because pytest will run that finalizer once it』s been added, 
even if that fixture raises an exception after adding the finalizer. 

這句話意思其實是說,當finalizer 一旦添加成功後,pytest就會去執行它。就算是fixture函數在添加了finalizer之後
拋出了異常。

按照這樣理解的話,那我在fixture函數中的程式碼就有問題了。因為db = DB()程式碼在request.addfinalizer(demo_finalizer)
之前就拋錯了,那麼實際上並沒有執行到添加終結器的這行程式碼,所以終結器都還沒添加成功,又怎麼會去執行呢?

終於我明白過來了,於是調整了程式碼順序,把request.addfinalizer(demo_finalizer)放到前面去,然後再接上fixture的程式碼:

# content of conftest.py
@pytest.fixture()
def init_data_allot_task(request):
    query_sql = """
    SELECT id FROM `sm_purchase_allot` WHERE `status`!=5 
    """
    def demo_finalizer():
        print("running finalizer code...")
    request.addfinalizer(demo_finalizer)
    print("running setup code...")

    db = DB()
    data = db.fetch_one(query_sql)
    db.close()
    return data

如此來看,我們會先看到"running setup code..."的輸出,然後看到mysql拋錯,
最後仍然可以看到"running setup code..."的輸出。

運行程式碼驗證一下:

這下就對了。