如何使用基於 unittest 的測試與 pytest

pytest 支援執行基於 Python unittest 的測試。它的用意是利用現有的基於 unittest 的測試套件,使用 pytest 作為測試執行器,並允許逐步調整測試套件以充分利用 pytest 的功能。

若要使用 pytest 執行現有的 unittest 風格測試套件,請輸入

pytest tests

pytest 會自動收集 unittest.TestCase 子類別及其 test 方法,這些方法位於 test_*.py*_test.py 檔案中。

幾乎所有 unittest 功能都受支援

  • @unittest.skip 風格的裝飾器;

  • setUp/tearDown;

  • setUpClass/tearDownClass;

  • setUpModule/tearDownModule;

此外,子測試pytest-subtests 外掛程式支援。

截至目前為止,pytest 尚未支援下列功能

開箱即用的好處

透過 pytest 執行測試套件,您可以使用多項功能,在多數情況下無需修改現有程式碼

pytest 功能在 unittest.TestCase 子類別中

下列 pytest 功能在 unittest.TestCase 子類別中運作

下列 pytest 功能運作,而且由於設計理念不同,可能永遠不會運作

第三方外掛程式是否運作良好,取決於外掛程式和測試套件。

使用標記將 pytest 固定裝置混合到 unittest.TestCase 子類別中

使用 pytest 執行您的 unittest,讓您可以在 unittest.TestCase 樣式測試中使用其 固定裝置機制。假設您至少瀏覽過 pytest 固定裝置功能,讓我們開始一個範例,整合 pytest db_class 固定裝置,設定一個類別快取資料庫物件,然後從 unittest 樣式測試中參照它

# content of conftest.py

# we define a fixture function below and it will be "used" by
# referencing its name from tests

import pytest


@pytest.fixture(scope="class")
def db_class(request):
    class DummyDB:
        pass

    # set a class attribute on the invoking test context
    request.cls.db = DummyDB()

這定義了一個固定裝置函數 db_class,如果使用,會對每個測試類別呼叫一次,並將類別層級的 db 屬性設定為 DummyDB 實例。固定裝置函數透過接收一個特殊的 request 物件來達成此目的,該物件可存取 要求測試內容,例如 cls 屬性,表示使用固定裝置的類別。此架構將固定裝置撰寫與實際測試程式碼分離,並允許透過最小的參考(固定裝置名稱)重複使用固定裝置。因此,讓我們使用我們的固定裝置定義撰寫一個實際的 unittest.TestCase 類別

# content of test_unittest_db.py

import unittest

import pytest


@pytest.mark.usefixtures("db_class")
class MyTest(unittest.TestCase):
    def test_method1(self):
        assert hasattr(self, "db")
        assert 0, self.db  # fail for demo purposes

    def test_method2(self):
        assert 0, self.db  # fail for demo purposes

類別裝飾器 @pytest.mark.usefixtures("db_class") 可確保每個類別呼叫一次 pytest 固定裝置函數 db_class。由於 assert 陳述式故意失敗,我們可以在追蹤記錄中查看 self.db

$ pytest test_unittest_db.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items

test_unittest_db.py FF                                               [100%]

================================= FAILURES =================================
___________________________ MyTest.test_method1 ____________________________

self = <test_unittest_db.MyTest testMethod=test_method1>

    def test_method1(self):
        assert hasattr(self, "db")
>       assert 0, self.db  # fail for demo purposes
E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E       assert 0

test_unittest_db.py:11: AssertionError
___________________________ MyTest.test_method2 ____________________________

self = <test_unittest_db.MyTest testMethod=test_method2>

    def test_method2(self):
>       assert 0, self.db  # fail for demo purposes
E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E       assert 0

test_unittest_db.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
============================ 2 failed in 0.12s =============================

此預設 pytest 追蹤記錄顯示兩個測試方法共用同一個 self.db 實例,這是我們在撰寫上述類別範圍固定裝置函數時的用意。

使用自動使用固定裝置和存取其他固定裝置

雖然通常最好明確宣告在特定測試中需要的固定裝置,但有時您可能希望在特定內容中自動使用固定裝置。畢竟,unittest 設定的傳統樣式強制使用此隱式固定裝置撰寫,而且您可能已經習慣或喜歡它。

您可以使用 @pytest.fixture(autouse=True) 標記固定裝置函數,並在您希望使用它的內容中定義固定裝置函數。讓我們看看一個 initdir 固定裝置,它會讓 TestCase 類別的所有測試方法在具有預先初始化的 samplefile.ini 的臨時目錄中執行。我們的 initdir 固定裝置本身使用 pytest 內建 tmp_path 固定裝置來委派每個測試臨時目錄的建立

# content of test_unittest_cleandir.py
import unittest

import pytest


class MyTest(unittest.TestCase):
    @pytest.fixture(autouse=True)
    def initdir(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)  # change to pytest-provided temporary directory
        tmp_path.joinpath("samplefile.ini").write_text("# testdata", encoding="utf-8")

    def test_method(self):
        with open("samplefile.ini", encoding="utf-8") as f:
            s = f.read()
        assert "testdata" in s

由於 autouse 旗標,initdir 固定函式將用於定義它的類別的所有方法。這是使用 @pytest.mark.usefixtures("initdir") 標記在類別上的捷徑,就像在先前的範例中。

執行這個測試模組 …

$ pytest -q test_unittest_cleandir.py
.                                                                    [100%]
1 passed in 0.12s

… 給我們一個通過的測試,因為 initdir 固定函式在 test_method 之前執行。

注意

unittest.TestCase 方法無法直接接收固定引數,因為實作這項功能可能會影響執行一般 unittest.TestCase 測試套件的能力。

上述 usefixturesautouse 範例應該有助於將 pytest 固定功能混合到 unittest 套件中。

您也可以逐漸從 unittest.TestCase 的子類別轉移到單純的斷言,然後開始逐步受益於完整的 pytest 功能集。

注意

由於這兩個架構之間的架構差異,unittest 為基礎的測試的設定和清除是在測試的 call 階段執行,而不是在 pytest 的標準 setupteardown 階段執行。在某些情況下,特別是在推理錯誤時,了解這一點很重要。例如,如果 unittest 為基礎的套件在設定期間出現錯誤,pytest 將在其 setup 階段報告沒有錯誤,而是在 call 期間引發錯誤。