这一篇来看pluggy在pytest里的具体应用。
pytest的主要功能可以分为用例的收集、fixture进行依赖注入,测试报告等等。那这篇就以fixture功能的实现作为切入点来看一下pytest是如何运用pluggy的。
主要内容:
- fixture的使用方式
- fixture的实现方式
fixture的使用方式
测试函数可以通过接受一个已经命名的fixture对象来使用他们。对于每个参数名,如果fixture已经声
明定义,会自动创建一个实例并传入该测试函数。fixture函数通过装饰器标志@pytest.fixture来注
册。下面是一个简单的独立的测试模块,包含一个fixture及使用它的测试函数
# ./test_smtpsimple.py
import pytest
@pytest.fixture
def smtp_connection():
import smtplib
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert 0 # 用于调试
这里,test_ehlo需要smtp_connection这个fixture的返回。pytest会在@pytest.fixture的fixture中
查找并调用名为smtp_connection的fixture。
fixture的实现方式
在pytest的 hookspec中有这样一个hookspec:
@hookspec(firstresult=True)
def pytest_fixture_setup(fixturedef, request):
""" performs fixture setup execution.
:return: The return value of the call to the fixture function
Stops at first non-None result, see :ref:`firstresult`
.. note::
If the fixture function returns None, other implementations of
this hook function will continue to be called, according to the
behavior of the :ref:`firstresult` option.
"""
他的作用是得到fixture的返回值。
其中firstresult被设置为了Ture,那这样一个设置的含义是什么呢?
A hookspec can be marked such that when the hook is called the call loop will only invoke up to the first hookimpl which returns a result other then None.
也就是说只要有一个hookimpl返回了一个非空值,后续的hookimpl就不会被调用。不过hookwrapper仍然会被调用。
在setuponly.py中有这样一个hookimpl:
@pytest.hookimpl(hookwrapper=True)
def pytest_fixture_setup(fixturedef, request):
yield
if request.config.option.setupshow:
if hasattr(request, "param"):
# Save the fixture parameter so ._show_fixture_action() can
# display it now and during the teardown (in .finish()).
if fixturedef.ids:
if callable(fixturedef.ids):
fixturedef.cached_param = fixturedef.ids(request.param)
else:
fixturedef.cached_param = fixturedef.ids[request.param_index]
else:
fixturedef.cached_param = request.param
_show_fixture_action(fixturedef, "SETUP")
这是一个wrapper,这意味着他会在所有其他hookimpl被调用之前或之后调用。类似于切片编程的方式将他们包裹起来。不难发现这个impl的作用是在其他fixture的hookimpl调用后根据参数显示fixture的信息。
再来看到在setupplan.py中有这样一个hookimpl:
@pytest.hookimpl(tryfirst=True)
def pytest_fixture_setup(fixturedef, request):
# Will return a dummy fixture if the setuponly option is provided.
if request.config.option.setupplan:
my_cache_key = fixturedef.cache_key(request)
fixturedef.cached_result = (None, my_cache_key, None)
return fixturedef.cached_result
这个被标记为了tryfirst的hookimpl意味着他会在hook调用中被第一个执行。
By default hooks are called in LIFO (last in first out) registered order, however, a hookimpl can influence its call-time invocation position using special attributes. If marked with a
"tryfirst"
or"trylast"
option it will be executed first or last respectively in the hook call loop。
而又因为pytest_fixture_setup这个hookspec在定义的时候使用了firstresult=Ture, 这样结合起来,如果说setupplan被掉调用的话只会返回cached_result。
在pytest --help里面我们可以看到:
--setup-only only setup fixtures, do not execute tests.
--setup-show show setup of fixtures while executing tests.
--setup-plan show what fixtures and tests would be executed but don't execute anything.
这也对应了之前三个hookimpl的调用方式。
可以看到利用pluggy提供的功能, pytest可以根据参数灵活的改变行为而无需改动代码。
网友评论