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