插件編寫
很容易為你自己的項目實現[本地conftest插件或可以在許多項目中使用的可[安裝的插件,包括第三方項目。如果你只想使用但不能編寫插件,請參閱[安裝和使用插件。
插件包含一個或多個鈎子(hooks)方法函數。[編寫鈎子(hooks)方法解釋了如何自己編寫鈎子(hooks)方法函數的基礎知識和細節。pytest
通過調用以下插件的[指定掛鈎來實現配置,收集,運行和報告的所有方面:
- 內置插件:從pytest的內部
_pytest
目錄加載。 - 外部插件
- conftest.py plugins:在測試目錄中自動發現的模塊
原則上,每個鈎子(hooks)方法調用都是一個1:N
Python函數調用,其中N
是給定規范的已注冊實現函數的數量。所有規范和實現都遵循pytest_
前綴命名約定,使其易於區分和查找。
Pytest啟動時的插件發現順序
pytest
通過以下方式在工具啟動時加載插件模塊:
-
通過加載所有內置插件
-
通過加載通過[setuptools入口點注冊的所有插件。
-
通過預掃描選項的命令行並在實際命令行解析之前加載指定的插件。
-pname
-
通過
conftest.py
命令行調用推斷加載所有文件:-
如果未指定測試路徑,則使用當前dir作為測試路徑
-
如果存在,則加載
conftest.py
並test*/conftest.py
相對於第一個測試路徑的目錄部分。請注意,pytest
conftest.py
在工具啟動時沒有在更深的嵌套子目錄中找到文件。將conftest.py
文件保存在頂級測試或項目根目錄中通常是個好主意。
-
-
通過遞歸加載文件中
pytest_plugins
變量指定的所有插件conftest.py
conftest.py:本地每目錄插件
本地conftest.py
插件包含特定於目錄的鈎子(hooks)方法實現。Hook Session和測試運行活動將調用conftest.py
靠近文件系統根目錄的文件中定義的所有掛鈎。實現pytest_runtest_setup
鈎子(hooks)方法的示例,以便在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”可能不明確,因為conftest.py
你PYTHONPATH
或者也可能有其他文件sys.path
。因此,項目要么放在conftest.py
包范圍內,要么永遠不從conftest.py
文件中導入任何內容,這是一種很好的做法。
另請參見: pytest import mechanisms和sys.path / PYTHONPATH。
編寫自己的插件
如果你想編寫插件,可以從中復制許多現實示例如:
- 自定義集合示例插件: 在Yaml文件中指定測試的基本示例
- 內置插件,提供pytest自己的函數
- 許多外部插件提供額外的函數
所有這些插件都實現了[鈎子(hooks)方法以擴展和添加函數。
注意
請務必查看優秀[的cookiecutter-pytest-plugin。
該模板提供了一個很好的起點,包括一個工作插件,使用tox運行的測試,一個全面的README文件以及一個預先配置的入口點。
另外考慮[將你的插件貢獻給pytest-dev一旦它擁有一些非自己的快樂用戶。
使你的插件可以被他人安裝
如果你想讓你的插件在外部可用,你可以為你的發行版定義一個所謂的入口點,以便pytest
找到你的插件模塊。入口點是[setuptools。pytest查找pytest11
入口點以發現其插件,因此你可以通過在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分類器
斷言重寫
其中一個主要特性pytest
是使用普通的斷言語句以及斷言失敗時表達式的詳細內省。這是由“斷言重寫”提供的,它在編譯為字節碼之前修改了解析的AST。這是通過一個完成的PEP 302導入掛鈎,在pytest
啟動時及早安裝,並在導入模塊時執行此重寫。但是,由於我們不想測試不同的字節碼,因此你將在生產中運行此掛鈎僅重寫測試模塊本身以及作為插件一部分的任何模塊。任何其他導入的模塊都不會被重寫,並且會發生正常的斷言行為。
如果你在其他模塊中有斷言助手,你需要啟用斷言重寫,你需要pytest
在導入之前明確要求重寫這個模塊。
注冊一個或多個要在導入時重寫的模塊名稱。
此函數將確保此模塊或程序包內的所有模塊將重寫其assert語句。因此,你應確保在實際導入模塊之前調用此方法,如果你是使用包的插件,則通常在__init__.py中調用。
拋出: TypeError- 如果給定的模塊名稱不是字符串。
當你編寫使用包創建的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
被重寫。如果輔助模塊還包含需要重寫的斷言語句,則需要在導入之前將其標記為這樣。通過將其標記為在__init__.py
模塊內部進行重寫,這是最簡單的,當導入包中的模塊時,將始終首先導入該模塊。這種方式plugin.py
仍然可以helper.py
正常導入。然后,內容pytest_foo/__init__.py
將需要如下所示:
import pytest
pytest.register_assert_rewrite("pytest_foo.helper")
在測試模塊或conftest文件中要求/加載插件
你可以在測試模塊或這樣的conftest.py
文件中要求插件:
pytest_plugins = ["name1","name2"]
加載測試模塊或conftest插件時,也會加載指定的插件。任何模塊都可以作為插件祝福,包括內部應用程序模塊:
pytest_plugins = "myapp.testsupport.myplugin"
pytest_plugins
變量是遞歸處理的,所以請注意,在上面的示例中,如果myapp.testsupport.myplugin
也聲明pytest_plugins
,變量的內容也將作為插件加載,依此類推。
注意
pytest_plugins
不建議使用非根conftest.py
文件中使用變量的插件。
這很重要,因為conftest.py
文件實現了每個目錄的鈎子(hooks)方法實現,但是一旦導入了插件,它就會影響整個目錄樹。為了避免混淆,不推薦pytest_plugins
在任何conftest.py
不在測試根目錄中的文件中進行定義,並將發出警告。
這種機制使得在應用程序甚至外部應用程序中共享Fixture方法變得容易,而無需使用setuptools
入口點技術創建外部插件。
導入的插件pytest_plugins
也會自動標記為斷言重寫(請參閱參考資料pytest.register_assert_rewrite()
在導入模塊之前自行調用,也可以安排代碼以延遲導入,直到注冊插件為止。
按名稱訪問另一個插件
如果一個插件想要與另一個插件的代碼協作,它可以通過插件管理器獲得一個引用,如下所示:
plugin = config.pluginmanager.get_plugin("name_of_plugin")
如果要查看現有插件的名稱,請使用該--trace-config
選項。
注冊為通用標記
測試插件
pytest附帶一個名為的插件pytester
,可幫助你為插件代碼編寫測試。默認情況下,該插件處於禁用狀態,因此你必須先啟用它,然后才能使用它。
你可以通過conftest.py
將以下行添加到測試目錄中的文件來執行此操作:
# content of conftest.py
pytest_plugins = ["pytester"]
或者,你可以使用命令行選項調用pytest。-ppytester
這將允許你使用testdir
fixture來測試你的插件代碼。
讓我們用一個例子演示你可以用插件做什么。想象一下,我們開發了一個插件,它提供了一個hello
產生函數的fixture,我們可以用一個可選參數調用這個函數。如果我們不提供值或者我們提供字符串值,它將返回字符串值。HelloWorld!``Hello{value}!
# -*- coding: utf-8 -*-
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
現在,testdir
fixture提供了一個方便的API來創建臨時conftest.py
文件和測試文件。它還允許我們運行測試並返回一個結果對象,通過它我們可以斷言測試的結果。
def test_hello(testdir):
"""Make sure that our plugin works."""
# create a temporary conftest.py file
testdir.makeconftest(
"""
import pytest
@pytest.fixture(params=[
"Brianna",
"Andreas",
"Floris",
])
def name(request):
return request.param
"""
)
# create a temporary pytest test file
testdir.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 = testdir.runpytest()
# check that all 4 tests passed
result.assert_outcomes(passed=4)
另外,可以在運行pytest之前復制示例文件夾的示例
# content of pytest.ini
[pytest]
pytester_example_dir = .
# content of test_example.py
def test_plugin(testdir):
testdir.copy_example("test_example.py")
testdir.runpytest("-k","test_example")
def test_example():
pass
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y,pytest-4.x.y,py-1.x.y,pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR,inifile: pytest.ini
collected 2 items
test_example.py .. [100%]
============================= warnings summary =============================
test_example.py::test_plugin
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
testdir.copy_example("test_example.py")
-- Docs: https://docs.pytest.org/en/latest/warnings.html
=================== 2 passed,1 warnings in 0.12 seconds ===================
有關runpytest()
返回的結果對象及其提供的方法的更多信息,請查看RunResult
文檔。