我怎样才能重写这个夹具调用,这样它就不会被直接调用?

How can I rewrite this fixture call so it won't be called directly?

我在测试文件中定义了以下夹具:

import os
from dotenv import load_dotenv, find_dotenv
from packaging import version # for comparing version numbers

load_dotenv(find_dotenv())
VERSION = os.environ.get("VERSION")
API_URL = os.environ.get("API_URL")

@pytest.fixture()
def skip_before_version():
    """
    Creates a fixture that takes parameters
    skips a test if it depends on certain features implemented in a certain version

    :parameter target_version:
    :parameter type: string
    """
    def _skip_before(target_version):
        less_than = version.parse(current_version) < version.parse(VERSION) 
        return pytest.mark.skipif(less_than)
    return _skip_before


skip_before = skip_before_version()("0.0.1")

我想在某些测试中使用 skip_before 作为夹具。我这样称呼它:

#@skip_before_version("0.0.1")     # tried this before and got the same error, so tried reworking it...
@when(parsers.cfparse("{categories} are added as categories"))
def add_categories(skip_before, create_tree, categories):   # now putting the fixture alongside parameters
    pass

当我运行这个时,我得到以下错误:

Fixture "skip_before_version" called directly. Fixtures are not meant to be called directly,
but are created automatically when test functions request them as parameters.
See https://docs.pytest.org/en/stable/fixture.html for more information about fixtures, and
https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code.

这怎么还是直接调用?我该如何解决这个问题?

如果我理解你的目标是正确的,你希望能够跳过基于版本限制说明符的测试。有很多方法可以做到这一点;我可以建议一个 autouse fixture,它将根据自定义标记条件跳过测试。示例:

import os
import pytest
from packaging.specifiers import SpecifierSet


VERSION = "1.2.3"  # read from environment etc.


@pytest.fixture(autouse=True)
def skip_based_on_version_compat(request):
    # get the version_compat marker
    version_compat = request.node.get_closest_marker("version_compat")
    if version_compat is None:  # test is not marked
        return

    if not version_compat.args:  # no specifier passed to marker
        return

    spec_arg = version_compat.args[0]
    spec = SpecifierSet(spec_arg)

    if VERSION not in spec:
        pytest.skip(f"Current version {VERSION} doesn't match test specifiers {spec_arg!r}.")

夹具 skip_based_on_version_compat 将在每次测试时调用,但只有在测试标记为 @pytest.mark.version_compat 时才会执行某些操作。示例测试:

@pytest.mark.version_compat(">=1.0.0")
def test_current_gen():
    assert True


@pytest.mark.version_compat(">=2.0.0")
def test_next_gen():
    raise NotImplementedError()

使用VERSION = "1.2.3",第一个测试将被执行,第二个将被跳过。注意调用 pytest.skip 以立即跳过测试。在固定装置中返回 pytest.mark.skip 不会给您带来任何好处,因为标记已经在很久之前就已经被评估了。

此外,我注意到您正在编写小黄瓜测试(大概使用 pytest-bdd)。通过上述方法,跳过整个场景应该也是可以的:

@pytest.mark.version_compat(">=1.0.0")
@scenario("my.feature", "my scenario")
def test_scenario():
    pass

或者,您可以在功能文件中标记场景:

Feature: Foo
    Lorem ipsum dolor sit amet.

    @version_compat(">=1.0.0")
    Scenario: doing future stuff
        Given foo is implemented
        When I see foo
        Then I do bar

并使用 pytest-bdd-own hooks:

def pytest_bdd_apply_tag(tag, function):
    matcher = re.match(r'^version_compat\("(?P<spec_arg>.*)"\)$', tag)
    spec_arg = matcher.groupdict()["spec_arg"]
    spec = SpecifierSet(spec_arg)
    if VERSION not in spec:
        marker = pytest.mark.skip(
            reason=f"Current version {VERSION} doesn't match restriction {spec_arg!r}."
        )
        marker(function)
        return True

不幸的是,自定义固定装置和标记都无法在单个步骤中跳过(并且您仍然会跳过整个场景,因为它是小黄瓜中的原子测试单元)。我没有找到一种可靠的方法来使 pytest-bdd 步骤与 pytest 的东西成为朋友;看起来他们只是被忽略了。不过,您可以轻松地编写一个自定义装饰器来达到相同的目的:

import functools


def version_compat(spec_arg):

    def deco(func):

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            spec = SpecifierSet(spec_arg)
            if VERSION not in spec:
                pytest.skip(f"Current version {VERSION} doesn't match test specifiers {spec_arg!r}.")
            return func(*args, **kwargs)

        return wrapper

    return deco

在一个步骤中使用 version_compat deco:

@when('I am foo')
@version_compat(">=2.0.0")
def i_am_foo():
    ...

注意顺序 - 将装饰器放在 pytest-bdd 自己的东西之外不会触发它们(我想值得提出一个问题,但是嗯)。