Fixture
Fixture 是 pytest 用來準備測試環境的函式:例如建立測試資料、開啟資料庫連線、產生暫存檔,測試只要在參數列寫上 fixture 名稱,pytest 就會自動執行它並把結果傳進來。Fixture 讓準備與清理的程式碼可以重複使用,也是 pytest 與 unittest 最大的差異。
為什麼需要 fixture?
多個測試都需要同一份資料時,直接複製貼上會讓測試又長又難維護。改成 fixture:
import pytest
@pytest.fixture
def cart():
return {"apple": 30, "banana": 15}
def test_cart_total(cart):
assert sum(cart.values()) == 45
def test_cart_has_apple(cart):
assert "apple" in cart
pytest 看到 test_cart_total 的參數叫 cart,就去找同名的 fixture 執行,並把回傳值傳入。這種依名稱自動注入的方式稱為依賴注入(dependency injection)。
每個測試拿到的都是全新的 cart。即使某個測試修改了它,也不會影響其他測試,這是測試能彼此獨立的關鍵。
用 yield 做清理(teardown)
需要「用完要收拾」的資源,例如檔案、連線、啟動的伺服器,用 yield 取代 return:yield 之前是準備,之後是清理。
import sqlite3
import pytest
@pytest.fixture
def db():
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (name TEXT)")
yield conn # 把連線交給測試
conn.close() # 測試結束後一定會執行,即使測試失敗
def test_insert_user(db):
db.execute("INSERT INTO users VALUES ('Amy')")
assert db.execute("SELECT COUNT(*) FROM users").fetchone()[0] == 1
Fixture 的範圍(scope)
預設每個測試都會重新執行 fixture(scope="function")。建立成本很高的資源,例如資料庫或瀏覽器,可以放大範圍讓多個測試共用:
| scope | 建立次數 |
|---|---|
function(預設) |
每個測試一次 |
class |
每個測試類別一次 |
module |
每個 .py 檔一次 |
package |
每個套件(資料夾)一次 |
session |
整次 pytest 執行只建立一次 |
共用範圍越大,越要小心狀態外洩
session 範圍的物件被某個測試修改後,後面的測試會看到修改後的狀態,造成「單獨跑會過、一起跑會失敗」的問題。共用的資源最好是唯讀的,或在每個測試前重設。
Fixture 可以依賴其他 fixture
Fixture 的參數也能是其他 fixture,pytest 會自動依序建立:
@pytest.fixture
def user():
return {"name": "Amy", "vip": True}
@pytest.fixture
def vip_cart(user, cart):
return {"owner": user["name"], "items": cart, "discount": 10}
def test_vip_discount(vip_cart):
assert vip_cart["discount"] == 10
一個 fixture 只負責一件事,再組合起來,比寫一個什麼都做的大 fixture 更容易維護。
在多個檔案之間共用:conftest.py
寫在測試檔裡的 fixture 只有該檔能用。放進同一資料夾(或上層資料夾)的 conftest.py,該資料夾以下所有測試都能直接使用,不需要 import:
conftest.py 的更多用途見 設定檔與 conftest.py。
其他常用技巧
autouse=True:讓 fixture 自動套用到範圍內所有測試,不必寫在參數列,適合「每個測試前都要重設」的情況。請節制使用,否則讀測試時看不出有哪些隱藏的準備動作。@pytest.mark.usefixtures("名稱"):只需要 fixture 的副作用、不需要它的回傳值時使用。pytest --fixtures:列出目前可用的所有 fixture 與說明,包含 pytest 內建的 fixture。
推薦影音
pytest: everything you need to know about fixtures
pytest 核心開發者 Anthony Sottile 約 20 分鐘的 fixture 專題,逐一說明 fixture 的各種選項與使用情境,適合讀完本頁後用來複習與補充細節。
Sharing is Caring - Sharing pytest Fixtures(PyCascades 2023)
《Python Testing with pytest》作者 Brian Okken 的演講,說明如何把團隊寫好的 fixture 打包成 pytest 外掛,在多個專案之間共用,適合已熟悉 fixture、想進一步整理大型測試的讀者。