為您自己的項目實現(xiàn)本地 ?conftest
插件或可在許多項目(包括第三方項目)中使用的 pip 可安裝插件很容易。
一個插件包含一個或多個鉤子函數(shù)。pytest 通過調(diào)用以下插件的指定鉤子來實現(xiàn)配置、收集、運行和報告的各個方面:
_pytest
? 目錄加載。setuptools
入口點發(fā)現(xiàn)的模塊原則上,每個鉤子調(diào)用都是一個 ?1:N
? Python 函數(shù)調(diào)用,其中 ?N
是給定規(guī)范的已注冊實現(xiàn)函數(shù)的數(shù)量。 所有規(guī)范和實現(xiàn)都遵循 ?pytest_
? 前綴命名約定,便于區(qū)分和查找。
pytest 在工具啟動時通過以下方式加載插件模塊:
-p no:name
? 選項并阻止加載該插件(即使是內(nèi)置插件也可以通過這種方式阻止)。 這發(fā)生在正常的命令行解析之前。-p name
? 選項并加載指定的插件。 這發(fā)生在正常的命令行解析之前。setuptools
入口點注冊的所有插件。PYTEST_PLUGINS
環(huán)境變量指定的所有插件。conftest.py
? 文件:如果沒有指定測試路徑,則使用當(dāng)前目錄作為測試路徑
conftest.py
? 和? test*/conftest.py
?相對于第一個測試路徑的目錄部分。加載 ?conftest.py
? 文件后,加載其 ?pytest_plugins
? 變量中指定的所有插件(如果存在)。conftest.py
? 文件。 將 ?conftest.py
? 文件保存在頂層測試或項目根目錄中通常是個好主意。conftest.py
? 文件中 ?pytest_plugins
? 變量指定的所有插件。本地 ?conftest.py
? 插件包含特定于目錄的鉤子實現(xiàn)。 鉤子會話和測試運行活動將調(diào)用 ?conftest.py
? 文件中定義的所有鉤子,這些鉤子更靠近文件系統(tǒng)的根目錄。 實現(xiàn) ?pytest_runtest_setup
?鉤子的示例,以便在a
子目錄中調(diào)用測試但不為其他目錄調(diào)用:
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
?”可能會產(chǎn)生歧義,因為在你的?PYTHONPATH
?或?sys.path
?中也可能有其他?conftest.py
?文件。 因此,項目將 ?conftest.py
? 放在包范圍內(nèi)或從不從 ?conftest.py
? 文件中導(dǎo)入任何內(nèi)容是一種很好的做法。
由于pytest在啟動過程中發(fā)現(xiàn)插件的方式,一些鉤子應(yīng)該只在?plugins
或位于?tests
?根目錄下的?conftest.py
?文件中實現(xiàn)。
如果你想寫一個插件,你可以復(fù)制很多現(xiàn)實生活中的例子:
所有這些插件都實現(xiàn)了鉤子and/or ?fixture
?來擴展和添加功能。
確保查看優(yōu)秀的 ?cookiecutter-pytest-plugin
? 項目,這是一個用于創(chuàng)作插件的 ?cookiecutter
模板。
該模板提供了一個很好的起點,其中包含一個工作插件、使用 ?tox
運行的測試、一個全面的 ?README
文件以及一個預(yù)配置的入口點。
也考慮將你的插件貢獻(xiàn)給 ?pytest-dev
? 一旦它有一些滿意的用戶而不是你自己。
如果你想讓你的插件在外部可用,你可以為你的發(fā)行版定義一個所謂的入口點,以便 pytest 找到你的插件模塊。 pytest 查找 ?pytest11
入口點以發(fā)現(xiàn)其插件,因此您可以通過在 ?setuptools-invocation
? 中定義它來使您的插件可用:
# sample ./setup.py file
from setuptools import setup
setup(
name="myproject",
packages=["myproject"],
# the following makes a plugin available to pytest
entry_points={"pytest11": ["name_of_plugin = myproject.pluginmodule"]},
# custom PyPI classifier for pytest plugins
classifiers=["Framework :: Pytest"],
)
如果以這種方式安裝包,pytest 將加載 ?myproject.pluginmodule
? 作為可以定義鉤子的插件。
確保在您的 PyPI 分類器列表中包含 ?Framework :: Pytest
?,以便用戶輕松找到您的插件。
pytest 的主要功能之一是使用簡單的斷言語句和斷言失敗時表達(dá)式的詳細(xì)自省。 這是由斷言重寫提供的,它在解析的 AST 被編譯為字節(jié)碼之前對其進(jìn)行修改。 這是通過 PEP 302 導(dǎo)入鉤子完成的,該鉤子在 pytest 啟動時盡早安裝,并在導(dǎo)入模塊時執(zhí)行此重寫。 但是,由于我們不想測試與您將在生產(chǎn)中運行的字節(jié)碼不同的字節(jié)碼,因此此鉤子僅重寫測試模塊本身(由 ?python_files
配置選項定義)以及作為插件一部分的任何模塊。 任何其他導(dǎo)入的模塊都不會被重寫,并且會發(fā)生正常的斷言行為。
如果您在需要啟用斷言重寫的其他模塊中有斷言助手,則需要在導(dǎo)入之前明確要求 pytest 重寫此模塊。
注冊一個或多個要在導(dǎo)入時重寫的模塊名稱。
此函數(shù)將確保此模塊或包內(nèi)的所有模塊將重寫其斷言語句。 因此,您應(yīng)該確保在實際導(dǎo)入模塊之前調(diào)用它,如果您是使用包的插件,通常在您的 ?__init__.py
? 中。
raises
?:?TypeError
?– 如果給定的模塊名稱不是字符串。names (str)
None
?當(dāng)您編寫使用包創(chuàng)建的 pytest 插件時,這一點尤其重要。 導(dǎo)入鉤子僅將 ?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
?會被重寫。如果helper模塊還包含需要重寫的?assert
?語句,則在導(dǎo)入之前,需要將其標(biāo)記為?assert
?語句。最簡單的方法是在?__init__.py
?模塊中標(biāo)記它以便重寫,當(dāng)包中的模塊被導(dǎo)入時,?__init__.py
?模塊總是首先被導(dǎo)入的。這樣?plugin.py
?仍然可以正常導(dǎo)入?helper.py
?。?pytest_foo/__init__.py
?的內(nèi)容將需要看起來像這樣:
import pytest
pytest.register_assert_rewrite("pytest_foo.helper")
你可以使用?pytest_plugins
?在測試模塊或?conftest.py
?文件中?require
?插件:
pytest_plugins = ["name1", "name2"]
當(dāng)?test
?模塊或?conftest
?插件被加載時,指定的插件也會被加載。任何模塊都可以作為插件,包括應(yīng)用程序的內(nèi)部模塊:
pytest_plugins = "myapp.testsupport.myplugin"
?pytest_plugins
是遞歸處理的,所以注意上面的例子中如果?myapp.testsupport.myplugin
? 也聲明了?pytest_plugins
?,那么變量的內(nèi)容也會被加載為插件,以此類推。
不推薦使用在非根 ?conftest.py
? 文件中使用 ?pytest_plugins
? 變量的插件。
這很重要,因為 ?conftest.py
? 文件實現(xiàn)了每個目錄的鉤子實現(xiàn),但是一旦插件被導(dǎo)入,它將影響整個目錄樹。 為了避免混淆,不推薦在任何不在測試根目錄中的 ?conftest.py
? 文件中定義 ?pytest_plugins
?,并且會引發(fā)警告。
這種機制使得在應(yīng)用程序甚至外部應(yīng)用程序中共享?fixture
?變得很容易,而不需要使用?setuptools
?的入口點技術(shù)創(chuàng)建外部插件。
?pytest_plugins
導(dǎo)入的插件也將自動標(biāo)記為斷言重寫。 但是,要使該模塊生效,必須先不導(dǎo)入該模塊; 如果在處理 ?pytest_plugins
語句時它已經(jīng)被導(dǎo)入,則會產(chǎn)生警告,并且插件內(nèi)的斷言將不會被重寫。 要解決此問題,您可以在導(dǎo)入模塊之前自己調(diào)用 ?pytest.register_assert_rewrite()
? ,或者您可以安排代碼延遲導(dǎo)入,直到插件注冊后。
如果一個插件想要與另一個插件的代碼協(xié)作,它可以通過插件管理器獲取引用,如下所示:
plugin = config.pluginmanager.get_plugin("name_of_plugin")
如果要查看現(xiàn)有插件的名稱,請使用 ?--trace-config
? 選項。
如果您的插件使用任何標(biāo)記,您應(yīng)該注冊它們,以便它們出現(xiàn)在 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
的插件,可幫助您為插件代碼編寫測試。 該插件默認(rèn)禁用,因此您必須先啟用它才能使用它。
您可以通過將以下行添加到測試目錄中的 ?conftest.py
? 文件中來做到這一點:
# content of conftest.py
pytest_plugins = ["pytester"]
或者,您可以使用?-p pyteste
?r命令行選項調(diào)用pytest。
這將允許您使用?pytester fixture
?來測試您的插件代碼。
讓我們用一個例子來演示你可以用這個插件做什么。假設(shè)我們開發(fā)了一個插件,它提供一個?fixture hello
?,該?fixture
?生成一個函數(shù),我們可以用一個可選參數(shù)調(diào)用這個函數(shù)。它將返回一個字符串值?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 "Hello {name}!".format(name=name)
return _hello
現(xiàn)在,?pytester fixture
為創(chuàng)建臨時?conftest.py
?文件和測試文件提供了一個方便的API。它還允許我們運行測試并返回一個結(jié)果對象,通過這個對象我們可以斷言測試的結(jié)果。
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 之前,可以將示例復(fù)制到 ?pytester
的隔離環(huán)境中。 這樣我們可以將測試的邏輯抽象到單獨的文件中,這對于更長的測試和/或更長的 ?conftest.py
? 文件特別有用。
請注意,要使 ?pytester.copy_example
? 正常工作,我們需要在 ?pytest.ini
? 中設(shè)置 ?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-7.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 =============================
更多建議: