測試類別和方法

已完成

除了撰寫測試函式之外,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

使用類別而非函式的時機

使用類別而非函式的時機並沒有任何嚴格的規則。 遵循目前專案以及您共事之小組中的慣例,一直是個好主意。 以下是可協助您判斷何時使用類別的一般問題:

  • 您的測試是否需要類似的設定或清除協助程式程式碼?
  • 將您的測試群組在一起是否符合邏輯?
  • 您的測試套件中至少有一些測試嗎?
  • 您的測試能否受益於一組常見的協助程式函式?