如何將基於 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 在這方面非常有幫助);
unittest.TestCase
子類別中的 pytest 功能¶
以下 pytest 功能在 unittest.TestCase
子類別中有效
由於不同的設計理念,以下 pytest 功能不起作用,而且可能永遠不會起作用
第三方插件可能運作良好,也可能無法良好運作,具體取決於插件和測試套件。
使用標記將 pytest fixtures 混合到 unittest.TestCase
子類別中¶
使用 pytest
執行 unittest 允許您將其fixture 機制 與 unittest.TestCase
風格的測試一起使用。假設您至少略讀了 pytest fixture 功能,讓我們直接跳到一個範例,該範例整合了 pytest db_class
fixture,設定了類別快取的資料庫物件,然後從 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()
這定義了一個 fixture 函數 db_class
,如果使用,則每個測試類別調用一次,並將類別級別的 db
屬性設定為 DummyDB
實例。fixture 函數透過接收特殊的 request
物件來實現此目的,該物件提供對請求測試上下文的存取權,例如 cls
屬性,表示使用 fixture 的類別。這種架構將 fixture 編寫與實際測試程式碼分離,並允許透過最小的引用(fixture 名稱)重複使用 fixture。因此,讓我們編寫一個實際的 unittest.TestCase
類別,使用我們的 fixture 定義
# 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 fixture 函數 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
實例,這是我們在編寫類別範圍的 fixture 函數時的意圖。
使用 autouse fixtures 和存取其他 fixtures¶
雖然通常最好明確聲明給定測試所需的 fixtures,但有時您可能希望擁有在給定上下文中自動使用的 fixtures。畢竟,傳統的 unittest 設定風格要求使用這種隱含的 fixture 編寫,而且您可能已經習慣或喜歡它。
您可以使用 @pytest.fixture(autouse=True)
標記 fixture 函數,並在您希望使用它的上下文中定義 fixture 函數。讓我們看一下 initdir
fixture,它使 TestCase
類別的所有測試方法都在具有預先初始化的 samplefile.ini
的臨時目錄中執行。我們的 initdir
fixture 本身使用 pytest 內建的 tmp_path
fixture 來委派建立每個測試的臨時目錄
# 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
fixture 函數將用於定義它的類別的所有方法。這是使用類別上的 @pytest.mark.usefixtures("initdir")
標記的快捷方式,就像前面的範例一樣。
執行此測試模組…
$ pytest -q test_unittest_cleandir.py
. [100%]
1 passed in 0.12s
… 給我們一個通過的測試,因為 initdir
fixture 函數在 test_method
之前執行。
注意
unittest.TestCase
方法無法直接接收 fixture 參數,因為實作這一點可能會影響執行一般 unittest.TestCase 測試套件的能力。
上面的 usefixtures
和 autouse
範例應有助於將 pytest fixtures 混合到 unittest 套件中。
您也可以逐步擺脫從 unittest.TestCase
子類別化的方式,轉向純粹的 assert,然後逐步開始受益於完整的 pytest 功能集。
注意
由於兩個框架之間架構上的差異,基於 unittest
的測試的設定和拆解是在測試的 call
階段執行的,而不是在 pytest
的標準 setup
和 teardown
階段。這在某些情況下可能很重要,尤其是在推理錯誤時。例如,如果基於 unittest
的套件在設定期間顯示錯誤,則 pytest
將在其 setup
階段報告沒有錯誤,而是在 call
期間引發錯誤。