如何使用基於 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 執行測試套件,您可以使用多項功能,在多數情況下無需修改現有程式碼
取得 更具資訊性的追蹤回溯;
stdout 和 stderr 擷取;
測試選項使用
-k
和-m
旗標;使用 pytest-xdist 外掛程式將測試分發到多個 CPU;
使用 純粹的 assert 語句,而不是
self.assert*
函數(unittest2pytest 在這方面非常有幫助);
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 測試套件的能力。
上述 usefixtures
和 autouse
範例應該有助於將 pytest 固定功能混合到 unittest 套件中。
您也可以逐漸從 unittest.TestCase
的子類別轉移到單純的斷言,然後開始逐步受益於完整的 pytest 功能集。
注意
由於這兩個架構之間的架構差異,unittest
為基礎的測試的設定和清除是在測試的 call
階段執行,而不是在 pytest
的標準 setup
和 teardown
階段執行。在某些情況下,特別是在推理錯誤時,了解這一點很重要。例如,如果 unittest
為基礎的套件在設定期間出現錯誤,pytest
將在其 setup
階段報告沒有錯誤,而是在 call
期間引發錯誤。