#python #pytest #decorator #fixtures
#python #pytest #декоратор #приспособления
Вопрос:
Я определил следующее приспособление в тестовом файле:
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.
Как это все еще вызывается напрямую? Как я могу это исправить?
Комментарии:
1. Как это все еще вызывается напрямую? — из-за
skip_before = skip_before_version("0.0.1")
строки. Как я могу это исправить? — из кода не совсем ясно, чего вы пытаетесь достичь, и код выглядит сломанным. Например.skip_before_version
не ожидает никаких аргументов, но вы передаете ему «0.0.1».2. @hoefling Вы правы, я должен позвонить
skip_before_version
, прежде чем передавать aruments функции, которую он возвращает, т.Е.skip_before = skip_before_version()("0.0.1")
.3. @hoefling Я все еще не уверен, как использовать этот инструмент, который пропустит тест, если версия недостаточно высока (это означает, что он должен принимать номер версии в качестве аргумента). Я не могу добавить этот параметр в качестве параметра в функцию, поскольку это недопустимый синтаксис, и я не могу вызвать его напрямую.
Ответ №1:
Если я правильно понимаю вашу цель, вы хотите иметь возможность пропускать тесты на основе спецификатора ограничения версии. Есть много способов сделать это; Я могу предложить автоматическое устройство, которое пропустит тест на основе пользовательского условия маркера. Пример:
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
в fixture ничего вам не принесет, поскольку маркеры уже были оценены задолго до этого.
Кроме того, я заметил, что вы пишете тесты gherkin (используя 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
собственные хуки:
def pytest_bdd_apply_tag(tag, function):
matcher = re.match(r'^version_compat("(?P<spec_arg>.*)")
К сожалению, ни пользовательские приспособления, ни маркеры не будут работать с пропуском за один шаг (и вы все равно будете пропускать весь сценарий, поскольку это атомарный тестовый модуль в gherkin). Я не нашел надежного способа подружить 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
собственного материала не вызовет их (я думаю, стоит открыть проблему, но meh).
К сожалению, ни пользовательские приспособления, ни маркеры не будут работать с пропуском за один шаг (и вы все равно будете пропускать весь сценарий, поскольку это атомарный тестовый модуль в gherkin). Я не нашел надежного способа подружить
, 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; похоже, они просто игнорируются. Тем не менее, вы можете легко написать пользовательский декоратор, служащий той же цели:
Использование version_compat
deco на шаге:
Обратите внимание на порядок — размещение декораторов за пределами pytest-bdd
собственного материала не вызовет их (я думаю, стоит открыть проблему, но meh).