pluggy框架学习笔记(二)

2020-07-09  本文已影响0人  landscape_f117

这一篇来看pluggy在pytest里的具体应用。

pytest的主要功能可以分为用例的收集、fixture进行依赖注入,测试报告等等。那这篇就以fixture功能的实现作为切入点来看一下pytest是如何运用pluggy的。

主要内容:

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可以根据参数灵活的改变行为而无需改动代码。

上一篇下一篇

猜你喜欢

热点阅读