入門

安裝 pytest

pytest 需要:Python 3.8+ 或 PyPy3。

  1. 在您的命令列執行下列命令

pip install -U pytest
  1. 檢查您是否安裝了正確的版本

$ pytest --version
pytest 8.2.0

建立您的第一個測試

建立一個名為 test_sample.py 的新檔案,其中包含一個函數和一個測試

# content of test_sample.py
def func(x):
    return x + 1


def test_answer():
    assert func(3) == 5

測試

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

test_sample.py F                                                     [100%]

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================

[100%] 指的是執行所有測試案例的整體進度。完成後,pytest 會顯示一個失敗報告,因為 func(3) 沒有傳回 5

注意

您可以使用 assert 陳述來驗證測試預期。pytest 的 進階斷言內省 將會智慧地報告斷言表達式的中間值,這樣您就可以避免許多名稱 JUnit 舊方法

執行多個測試

pytest 將會執行目前目錄及其子目錄中所有形式為 test_*.py 或 *_test.py 的檔案。更一般地來說,它遵循 標準測試發現規則

斷言引發某個例外

使用 raises 輔助程式來斷言某個程式碼引發例外

# content of test_sysexit.py
import pytest


def f():
    raise SystemExit(1)


def test_mytest():
    with pytest.raises(SystemExit):
        f()

您也可以使用 raises 提供的內容來斷言預期的例外是引發的 ExceptionGroup 的一部分

# content of test_exceptiongroup.py
import pytest


def f():
    raise ExceptionGroup(
        "Group message",
        [
            RuntimeError(),
        ],
    )


def test_exception_in_group():
    with pytest.raises(ExceptionGroup) as excinfo:
        f()
    assert excinfo.group_contains(RuntimeError)
    assert not excinfo.group_contains(TypeError)

使用「安靜」報告模式執行測試函數

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

注意

-q/--quiet 旗標在這個和後面的範例中保持簡短的輸出。

在類別中群組多個測試

一旦開發多個測試,您可能想要將它們群組到類別中。pytest 使得建立包含多個測試的類別變得容易

# content of test_class.py
class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")

pytest 發現所有遵循其 Python 測試發現慣例 的測試,因此它找到兩個 test_ 前綴函數。不需要繼承任何子類別,但請務必使用 Test 為您的類別加上前綴,否則類別將會被略過。我們可以透過傳遞其檔名來執行模組

$ pytest -q test_class.py
.F                                                                   [100%]
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________

self = <test_class.TestClass object at 0xdeadbeef0001>

    def test_two(self):
        x = "hello"
>       assert hasattr(x, "check")
E       AssertionError: assert False
E        +  where False = hasattr('hello', 'check')

test_class.py:8: AssertionError
========================= short test summary info ==========================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.12s

第一個測試通過,第二個測試失敗。您可以在斷言中輕鬆看到中間值,以幫助您了解失敗的原因。

將測試群組到類別中可能會有以下好處

  • 測試組織

  • 僅在特定類別中分享測試固定裝置

  • 在類別層級套用標記,並讓它們隱含套用至所有測試

在類別中群組測試時要注意的一點是,每個測試都有類別的唯一實例。讓每個測試分享相同的類別實例將會嚴重損害測試隔離,並會助長不良的測試實務。這在下面有說明

# content of test_class_demo.py
class TestClassDemoInstance:
    value = 0

    def test_one(self):
        self.value = 1
        assert self.value == 1

    def test_two(self):
        assert self.value == 1
$ pytest -k TestClassDemoInstance -q
.F                                                                   [100%]
================================= FAILURES =================================
______________________ TestClassDemoInstance.test_two ______________________

self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>

    def test_two(self):
>       assert self.value == 1
E       assert 0 == 1
E        +  where 0 = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>.value

test_class_demo.py:9: AssertionError
========================= short test summary info ==========================
FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 == 1
1 failed, 1 passed in 0.12s

請注意,在類別層級新增的屬性是類別屬性,因此它們將在測試之間分享。

要求功能測試的唯一暫時目錄

pytest 提供 內建固定裝置/函數引數 來要求任意資源,例如唯一的暫時目錄

# content of test_tmp_path.py
def test_needsfiles(tmp_path):
    print(tmp_path)
    assert 0

在測試函數簽章中列出名稱 tmp_pathpytest 將會查詢並呼叫固定裝置工廠,以便在執行測試函數呼叫之前建立資源。在測試執行之前,pytest 會建立每個測試呼叫唯一的暫時目錄

$ pytest -q test_tmp_path.py
F                                                                    [100%]
================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________

tmp_path = PosixPath('PYTEST_TMPDIR/test_needsfiles0')

    def test_needsfiles(tmp_path):
        print(tmp_path)
>       assert 0
E       assert 0

test_tmp_path.py:3: AssertionError
--------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0
========================= short test summary info ==========================
FAILED test_tmp_path.py::test_needsfiles - assert 0
1 failed in 0.12s

可以在 暫時目錄和檔案 找到有關暫時目錄處理的更多資訊。

使用指令找出有哪些內建 pytest 固定裝置

pytest --fixtures   # shows builtin and custom fixtures

請注意,此指令會省略前導 _ 的固定裝置,除非加入 -v 選項。

繼續閱讀

檢閱其他 pytest 資源,以協助您自訂測試,以符合您獨特的作業流程