撰寫外掛程式¶
可以輕鬆實作專案的 本地 conftest 外掛程式 或 可透過 pip 安裝的外掛程式,後者可用於許多專案,包括第三方專案。如果你只想使用外掛程式,但不想撰寫外掛程式,請參閱 如何安裝和使用外掛程式。
外掛程式包含一個或多個掛勾函式。 撰寫掛勾 說明如何撰寫掛勾函式的基礎知識和詳細資訊。 pytest
透過呼叫下列外掛程式的 明確指定的掛勾 來實作設定、收集、執行和報告的各個面向
內建外掛程式:從 pytest 內部的
_pytest
目錄載入。外部外掛程式:透過 setuptools 進入點 發現的模組
conftest.py 外掛程式:在測試目錄中自動發現的模組
原則上,每個掛勾呼叫都是 1:N
Python 函式呼叫,其中 N
是針對特定規格註冊的實作函式數目。所有規格和實作都遵循 pytest_
前置命名慣例,讓它們易於辨識和尋找。
工具啟動時的外掛程式偵測順序¶
pytest
在工具啟動時載入外掛程式模組,方式如下
掃描命令列的
-p no:name
選項,並封鎖該外掛程式載入(甚至內建外掛程式也可以用這種方式封鎖)。這會在一般命令列解析之前發生。載入所有內建外掛程式。
掃描命令列的
-p name
選項,並載入指定的外掛程式。這會在一般命令列解析之前發生。載入所有透過 setuptools 進入點 註冊的外掛程式。
透過載入所有透過
PYTEST_PLUGINS
環境變數指定的插件來載入。透過載入所有「初始」
conftest.py
檔案決定測試路徑:在命令列上指定,否則在
testpaths
中定義且從 rootdir 執行,否則為目前目錄對於每個測試路徑,載入
conftest.py
和test*/conftest.py
相對於測試路徑的目錄部分,如果存在的話。在載入conftest.py
檔案之前,載入其所有父目錄中的conftest.py
檔案。在載入conftest.py
檔案之後,遞迴載入其pytest_plugins
變數中指定的所有插件(如果存在)。
conftest.py:每個目錄的區域插件¶
區域 conftest.py
插件包含特定於目錄的掛勾實作。掛勾會話和測試執行活動將呼叫在更接近檔案系統根目錄的 conftest.py
檔案中定義的所有掛勾。實作 pytest_runtest_setup
掛勾的範例,以便在 a
子目錄中的測試呼叫,但不會呼叫其他目錄
a/conftest.py:
def pytest_runtest_setup(item):
# called for running each test in 'a' directory
print("setting up", item)
a/test_sub.py:
def test_sub():
pass
test_flat.py:
def test_flat():
pass
以下是如何執行它
pytest test_flat.py --capture=no # will not show "setting up"
pytest a/test_sub.py --capture=no # will show "setting up"
注意
如果您有 conftest.py
檔案,但它不在 Python 套件目錄中(即包含 __init__.py
的檔案),則「import conftest」可能會模稜兩可,因為您的 PYTHONPATH
或 sys.path
中也可能有其他 conftest.py
檔案。因此,專案的良好做法是將 conftest.py
放在套件範圍內,或從不從 conftest.py
檔案匯入任何內容。
注意
有些掛勾無法在不是 初始 的 conftest.py 檔案中實作,這是因為 pytest 在啟動期間偵測插件的方式。有關詳細資訊,請參閱每個掛勾的文件。
撰寫自己的外掛程式¶
如果您想撰寫外掛程式,有許多現實生活中的範例可以複製
自訂收集範例外掛程式:指定 Yaml 檔案中測試的基本範例
提供 pytest 自身功能的內建外掛程式
許多 外部外掛程式 提供其他功能
所有這些外掛程式都實作 掛勾 和/或 固定裝置 以擴充和新增功能。
注意
請務必查看優秀的 cookiecutter-pytest-plugin 專案,它是 cookiecutter 範本,用於撰寫外掛程式。
此範本提供一個優秀的起點,其中包含一個運作中的外掛程式、使用 tox 執行的測試、一個全面的 README 檔案以及一個預先設定的進入點。
此外,請考慮 將您的外掛程式貢獻給 pytest-dev,一旦它除了您之外還有一些滿意的使用者。
讓其他人可以安裝您的外掛程式¶
如果您想讓您的外掛程式在外部可用,您可以為您的發行版定義一個所謂的進入點,以便 pytest
找到您的外掛程式模組。進入點是由 setuptools 提供的功能。
pytest 查詢 pytest11
進入點以找出其外掛程式,因此您可以透過在 pyproject.toml
檔案中定義外掛程式,讓您的外掛程式可用。
# sample ./pyproject.toml file
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "myproject"
classifiers = [
"Framework :: Pytest",
]
[project.entry-points.pytest11]
myproject = "myproject.pluginmodule"
如果套件是以這種方式安裝的,pytest
會載入 myproject.pluginmodule
作為可定義 掛勾 的外掛程式。使用 pytest --trace-config
確認註冊。
注意
務必在 PyPI 分類器 清單中包含 Framework :: Pytest
,讓使用者可以輕鬆找到您的外掛程式。
宣告重寫¶
pytest
的主要功能之一是使用純粹的宣告陳述,以及在宣告失敗時詳細內省表達式。這是由「宣告重寫」提供的,它會在解析的 AST 編譯成位元組碼之前修改它。這是透過 PEP 302 匯入掛勾完成的,當 pytest
啟動時,它會在早期安裝,並在匯入模組時執行此重寫。然而,由於我們不希望測試與您在生產環境中執行的不同位元組碼,因此此掛勾只會重寫測試模組本身(由 python_files
組態選項定義),以及屬於外掛程式的任何模組。任何其他匯入的模組都不會被重寫,且會發生正常的宣告行為。
如果您在其他模組中有宣告輔助程式,而您需要啟用宣告重寫,則需要在匯入之前明確要求 pytest
重寫此模組。
- register_assert_rewrite(*names)[source]
註冊一個或多個模組名稱,以便在匯入時重寫。
此函式會確保此模組或套件內的所有模組都會重寫其宣告陳述。因此,您應該確保在實際匯入模組之前呼叫此函式,通常是在 __init__.py 中,如果您是使用套件的外掛程式。
- 參數:
names (str) – 要註冊的模組名稱。
當您使用套件建立 pytest 外掛程式時,這特別重要。匯入掛鉤只會處理 conftest.py
檔案和任何在 pytest11
進入點中列為外掛程式的模組。以下套件為例
pytest_foo/__init__.py
pytest_foo/plugin.py
pytest_foo/helper.py
具有以下一般 setup.py
摘錄
setup(..., entry_points={"pytest11": ["foo = pytest_foo.plugin"]}, ...)
在這種情況下,只有 pytest_foo/plugin.py
會被改寫。如果輔助模組也包含需要改寫的 assert 陳述式,則需要在匯入之前將其標記為這樣。最簡單的方法是在 __init__.py
模組中標記它以進行改寫,當匯入套件中的模組時,它將始終首先被匯入。這樣,plugin.py
仍然可以正常匯入 helper.py
。然後,pytest_foo/__init__.py
的內容需要如下所示
import pytest
pytest.register_assert_rewrite("pytest_foo.helper")
在測試模組或 conftest 檔案中要求/載入外掛程式¶
您可以使用 pytest_plugins
在測試模組或 conftest.py
檔案中要求外掛程式
pytest_plugins = ["name1", "name2"]
當載入測試模組或 conftest 外掛程式時,指定的外部程式也會被載入。任何模組都可以被認可為外掛程式,包括內部應用程式模組
pytest_plugins = "myapp.testsupport.myplugin"
pytest_plugins
會遞迴處理,因此請注意,在上面的範例中,如果 myapp.testsupport.myplugin
也宣告 pytest_plugins
,則變數的內容也會被載入為外掛程式,依此類推。
注意
在非根目錄的 conftest.py
檔案中使用 pytest_plugins
變數來要求外掛程式已不建議使用。
這很重要,因為 conftest.py
檔案實作每個目錄的掛鉤實作,但一旦匯入外掛程式,它將影響整個目錄樹。為了避免混淆,在任何未位於測試根目錄的 conftest.py
檔案中定義 pytest_plugins
已不建議使用,並且會產生警告。
此機制讓在應用程式或甚至外部應用程式中分享固定裝置變得容易,而不需要使用 setuptools
的進入點技術來建立外部外掛程式。
由 pytest_plugins
匯入的外掛程式也會自動標記為宣告重寫(請參閱 pytest.register_assert_rewrite()
)。但是,要產生任何效果,模組一定不能已經被匯入;如果在處理 pytest_plugins
陳述式時它已經被匯入,將會產生警告,而且外掛程式內的宣告不會被重寫。要修正這個問題,您可以在模組被匯入之前呼叫 pytest.register_assert_rewrite()
,或者您可以安排程式碼來延遲匯入,直到外掛程式被註冊之後。
依名稱存取另一個外掛程式¶
如果外掛程式想要與來自另一個外掛程式的程式碼協作,它可以透過外掛程式管理員取得參考,如下所示
plugin = config.pluginmanager.get_plugin("name_of_plugin")
如果您想要查看現有外掛程式的名稱,請使用 --trace-config
選項。
註冊自訂標記¶
如果你的外掛程式使用任何標記,你應該註冊它們,以便它們出現在 pytest 的說明文字中,且不會造成虛假警告。例如,下列外掛程式會註冊 cool_marker
和 mark_with
給所有使用者
def pytest_configure(config):
config.addinivalue_line("markers", "cool_marker: this one is for cool tests.")
config.addinivalue_line(
"markers", "mark_with(arg, arg2): this marker takes arguments."
)
測試外掛程式¶
pytest 附帶一個名為 pytester
的外掛程式,它可以協助你撰寫外掛程式程式碼的測試。此外掛程式預設為停用,因此你必須在使用前先啟用它。
你可以將下列程式碼加入測試目錄中的 conftest.py
檔案來執行此操作
# content of conftest.py
pytest_plugins = ["pytester"]
或者,你可以使用 -p pytester
命令列選項來呼叫 pytest。
這將允許你使用 pytester
固定裝置來測試你的外掛程式程式碼。
讓我們透過一個範例來說明你可以使用此外掛程式做什麼。假設我們開發了一個提供固定裝置 hello
的外掛程式,它會產生一個函式,而我們可以使用一個可選參數來呼叫此函式。如果我們未提供值,它會傳回 Hello World!
的字串值;如果我們提供字串值,它會傳回 Hello {value}!
。
import pytest
def pytest_addoption(parser):
group = parser.getgroup("helloworld")
group.addoption(
"--name",
action="store",
dest="name",
default="World",
help='Default "name" for hello().',
)
@pytest.fixture
def hello(request):
name = request.config.getoption("name")
def _hello(name=None):
if not name:
name = request.config.getoption("name")
return f"Hello {name}!"
return _hello
現在,pytester
固定裝置提供了一個便利的 API,可用於建立暫時的 conftest.py
檔案和測試檔案。它也允許我們執行測試並傳回一個結果物件,我們可以使用它來斷言測試結果。
def test_hello(pytester):
"""Make sure that our plugin works."""
# create a temporary conftest.py file
pytester.makeconftest(
"""
import pytest
@pytest.fixture(params=[
"Brianna",
"Andreas",
"Floris",
])
def name(request):
return request.param
"""
)
# create a temporary pytest test file
pytester.makepyfile(
"""
def test_hello_default(hello):
assert hello() == "Hello World!"
def test_hello_name(hello, name):
assert hello(name) == "Hello {0}!".format(name)
"""
)
# run all tests with pytest
result = pytester.runpytest()
# check that all 4 tests passed
result.assert_outcomes(passed=4)
此外,在對其執行 pytest 之前,也可以將範例複製到 pytester
的隔離環境中。這樣,我們可以將被測試的邏輯抽象到不同的檔案中,這對於較長的測試和/或較長的 conftest.py
檔案特別有用。
請注意,要讓 pytester.copy_example
正常運作,我們需要在 pytest.ini
中設定 pytester_example_dir
,以告訴 pytest 到哪裡尋找範例檔案。
# content of pytest.ini
[pytest]
pytester_example_dir = .
# content of test_example.py
def test_plugin(pytester):
pytester.copy_example("test_example.py")
pytester.runpytest("-k", "test_example")
def test_example():
pass
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
configfile: pytest.ini
collected 2 items
test_example.py .. [100%]
============================ 2 passed in 0.12s =============================
如需瞭解 runpytest()
回傳的結果物件和它提供的函式,請查看 RunResult
文件。