測試類別和方法
除了撰寫測試函式之外,Pytest 還可讓您使用類別。 如先前所述,您不需要繼承,測試類別也會遵循一些簡單的規則。 使用類別可讓您享有更大的彈性與重複使用性。 如您接下來所見,Pytest 不會干擾您,也不會強制您使用特定方法撰寫測試。
就像函式一樣,您仍然可以使用 assert
陳述式來撰寫判斷提示。
組建測試類別
讓我們使用實際案例看看測試類別如何協助您。 下列函式會檢查指定檔案的內容是否包含「是」。 如果是,則會傳回 True
。 如果檔案不存在或其內容包含「否」,則會傳回 False
。 此案例在使用檔案系統表示完成度的非同步工作中很常見。
函式顯示如下:
import os
def is_done(path):
if not os.path.exists(path):
return False
with open(path) as _f:
contents = _f.read()
if "yes" in contents.lower():
return True
elif "no" in contents.lower():
return False
內含兩種測試 (每個條件各一種) 的類別在名為「test_files.py」的檔案中將顯示如下:
class TestIsDone:
def test_yes(self):
with open("/tmp/test_file", "w") as _f:
_f.write("yes")
assert is_done("/tmp/test_file") is True
def test_no(self):
with open("/tmp/test_file", "w") as _f:
_f.write("no")
assert is_done("/tmp/test_file") is False
警告
測試方法使用「/tmp」路徑作為暫時測試檔案,因為這樣更容易示範。 不過,如果您需要使用暫存檔案,請考慮使用 tempfile
等程式庫,才能安全地建立 (和移除)。 並非所有系統都有「/tmp」目錄,視不同的作業系統而定,其位置也可能不是暫時的。
使用 -v
旗標執行測試,即可提升詳細程度並顯示通過的測試:
pytest -v test_files.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
cachedir: .pytest_cache
rootdir: /private/
collected 2 items
test_files.py::TestIsDone::test_yes PASSED [ 50%]
test_files.py::TestIsDone::test_no PASSED [100%]
============================== 2 passed in 0.00s ===============================
雖然測試已通過,但內容看起來重複,而且也會在測試結束後留下檔案。 在了解如何改善之前,我們先前往下個章節介紹協助程式方法。
Helper 方法
在測試類別中,您可以透過某些方法來設定和終止測試執行。 若其已定義,Pytest 會自動執行它們。 若要使用這些方法,您就應瞭解其特定的順序和行為。
setup
:在類別中的每個測試開始前執行一次teardown
:在類別中的每個測試開始後執行一次setup_class
:在類別中的所有測試開始前執行一次teardown_class
:在類別中的所有測試開始後執行一次
當測試需要類似 (或相同) 資源才能執行時,撰寫設定方法很實用。 理想的話,測試不應在完成後遺留資源,因此終止方法有助於在這些情況下清除測試檔案。
清理
我們來查看已更新的測試類別,它會在每次測試結束後清除檔案:
class TestIsDone:
def teardown(self):
if os.path.exists("/tmp/test_file"):
os.remove("/tmp/test_file")
def test_yes(self):
with open("/tmp/test_file", "w") as _f:
_f.write("yes")
assert is_done("/tmp/test_file") is True
def test_no(self):
with open("/tmp/test_file", "w") as _f:
_f.write("no")
assert is_done("/tmp/test_file") is False
因為我們使用 teardown()
方法,所以這個測試類別不會留下「/tmp/test_file」。
設定
我們改善此類別的另一個方式為新增一個指向檔案的變數。 由於檔案現在於六個位置皆進行宣告,因此路徑上的任何變更都會對所有位置造成影響。 此範例顯示在新增宣告路徑變數的 setup()
方法後,類別看起來如下:
class TestIsDone:
def setup(self):
self.tmp_file = "/tmp/test_file"
def teardown(self):
if os.path.exists(self.tmp_file):
os.remove(self.tmp_file)
def test_yes(self):
with open(self.tmp_file, "w") as _f:
_f.write("yes")
assert is_done(self.tmp_file) is True
def test_no(self):
with open(self.tmp_file, "w") as _f:
_f.write("no")
assert is_done(self.tmp_file) is False
自訂協助程式方法
您可以在類別中建立自訂協助程式方法。 這些方法不得以名稱 test
為前置詞,也不能以設定或清除方法命名。 在 TestIsDone
類別中,我們可以透過自訂協助程式自動建立暫存檔案。 該自訂協助程式方法看起來可能如同此範例:
def write_tmp_file(self, content):
with open(self.tmp_file, "w") as _f:
_f.write(content)
Pytest 不會自動執行 write_tmp_file()
方法,而其他方法可直接呼叫它,以儲存重複的工作,譬如寫入檔案。
更新測試方法並使用自訂協助程式之後,整個類別看起來如同此範例:
class TestIsDone:
def setup(self):
self.tmp_file = "/tmp/test_file"
def teardown(self):
if os.path.exists(self.tmp_file):
os.remove(self.tmp_file)
def write_tmp_file(self, content):
with open(self.tmp_file, "w") as _f:
_f.write(content)
def test_yes(self):
self.write_tmp_file("yes")
assert is_done(self.tmp_file) is True
def test_no(self):
self.write_tmp_file("no")
assert is_done(self.tmp_file) is False
使用類別而非函式的時機
使用類別而非函式的時機並沒有任何嚴格的規則。 遵循目前專案以及您共事之小組中的慣例,一直是個好主意。 以下是可協助您判斷何時使用類別的一般問題:
- 您的測試是否需要類似的設定或清除協助程式程式碼?
- 將您的測試群組在一起是否符合邏輯?
- 您的測試套件中至少有一些測試嗎?
- 您的測試能否受益於一組常見的協助程式函式?